- separated the Doom specific parts from the compiler backend into a separate file, these parts now get invoked via callback hooks.

This commit is contained in:
Christoph Oelckers 2020-04-11 19:31:58 +02:00
parent 64dc9ac456
commit 3454314bb1
14 changed files with 1164 additions and 895 deletions

View file

@ -1044,6 +1044,7 @@ set (PCH_SOURCES
scripting/thingdef_data.cpp
scripting/thingdef_properties.cpp
scripting/backend/codegen.cpp
scripting/backend/codegen_doom.cpp
scripting/backend/vmbuilder.cpp
scripting/decorate/olddecorations.cpp
scripting/decorate/thingdef_exp.cpp

View file

@ -104,3 +104,11 @@ inline float RAD2DEG(float deg)
#define SECTION_YREG "yreg"
#endif
// This is needed in common code, despite being Doom specific.
enum EStateUseFlags
{
SUF_ACTOR = 1,
SUF_OVERLAY = 2,
SUF_WEAPON = 4,
SUF_ITEM = 8,
};

View file

@ -74,14 +74,6 @@ enum EStateFlags
STF_DEHACKED = 64, // Modified by Dehacked
};
enum EStateUseFlags
{
SUF_ACTOR = 1,
SUF_OVERLAY = 2,
SUF_WEAPON = 4,
SUF_ITEM = 8,
};
enum EStateType : int // this must ensure proper alignment.
{
STATE_Actor,

View file

@ -38,22 +38,20 @@
*/
#include <stdlib.h>
#include "actor.h"
#include "cmdlib.h"
#include "a_pickups.h"
#include "thingdef.h"
#include "p_lnspec.h"
#include "codegen.h"
#include "v_text.h"
#include "filesystem.h"
#include "doomstat.h"
#include "g_levellocals.h"
#include "v_video.h"
#include "utf8.h"
#include "texturemanager.h"
#include "m_random.h"
#include "v_font.h"
#include "templates.h"
extern FRandom pr_exrandom;
FMemArena FxAlloc(65536);
CompileEnvironment compileEnvironment;
struct FLOP
{
@ -228,12 +226,6 @@ static PClass *FindClassType(FName name, FCompileContext &ctx)
return nullptr;
}
bool isActor(PContainerType *type)
{
auto cls = PType::toClass(type);
return cls ? cls->Descriptor->IsDescendantOf(RUNTIME_CLASS(AActor)) : false;
}
//==========================================================================
//
// ExpEmit
@ -272,7 +264,7 @@ void ExpEmit::Reuse(VMFunctionBuilder *build)
//
//==========================================================================
static PFunction *FindBuiltinFunction(FName funcname)
PFunction *FindBuiltinFunction(FName funcname)
{
return dyn_cast<PFunction>(RUNTIME_CLASS(DObject)->FindSymbol(funcname, true));
}
@ -1181,7 +1173,7 @@ ExpEmit FxNameCast::Emit(VMFunctionBuilder *build)
assert(ptr.RegType == REGT_POINTER);
ptr.Free(build);
ExpEmit to(build, REGT_INT);
build->Emit(OP_LW, to.RegNum, ptr.RegNum, build->GetConstantInt(myoffsetof(PClassActor, TypeName)));
build->Emit(OP_LW, to.RegNum, ptr.RegNum, build->GetConstantInt(myoffsetof(PClass, TypeName)));
return to;
}
}
@ -1244,7 +1236,7 @@ FxExpression *FxStringCast::Resolve(FCompileContext &ctx)
if (basex->isConstant())
{
ExpVal constval = static_cast<FxConstant *>(basex)->GetValue();
FxExpression *x = new FxConstant(S_GetSoundName(constval.GetInt()), ScriptPosition);
FxExpression *x = new FxConstant(soundEngine->GetSoundName(constval.GetInt()), ScriptPosition);
delete this;
return x;
}
@ -1559,6 +1551,12 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
CHECKRESOLVED();
SAFE_RESOLVE(basex, ctx);
if (compileEnvironment.SpecialTypeCast)
{
auto result = compileEnvironment.SpecialTypeCast(this, ctx);
if (result != this) return result;
}
// first deal with the simple types
if (ValueType == TypeError || basex->ValueType == TypeError || basex->ValueType == nullptr)
{
@ -1648,70 +1646,6 @@ FxExpression *FxTypeCast::Resolve(FCompileContext &ctx)
delete this;
return x;
}
else if (ValueType == TypeStateLabel)
{
if (basex->ValueType == TypeNullPtr)
{
auto x = new FxConstant(0, ScriptPosition);
x->ValueType = TypeStateLabel;
delete this;
return x;
}
// Right now this only supports string constants. There should be an option to pass a string variable, too.
if (basex->isConstant() && (basex->ValueType == TypeString || basex->ValueType == TypeName))
{
FString s= static_cast<FxConstant *>(basex)->GetValue().GetString();
if (s.Len() == 0 && !ctx.FromDecorate) // DECORATE should never get here at all, but let's better be safe.
{
ScriptPosition.Message(MSG_ERROR, "State jump to empty label.");
delete this;
return nullptr;
}
FxExpression *x = new FxMultiNameState(s, basex->ScriptPosition);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
return x;
}
else if (basex->IsNumeric() && basex->ValueType != TypeSound && basex->ValueType != TypeColor)
{
if (ctx.StateIndex < 0)
{
ScriptPosition.Message(MSG_ERROR, "State jumps with index can only be used in anonymous state functions.");
delete this;
return nullptr;
}
if (ctx.StateCount != 1)
{
ScriptPosition.Message(MSG_ERROR, "State jumps with index cannot be used on multistate definitions");
delete this;
return nullptr;
}
if (basex->isConstant())
{
int i = static_cast<FxConstant *>(basex)->GetValue().GetInt();
if (i <= 0)
{
ScriptPosition.Message(MSG_ERROR, "State index must be positive");
delete this;
return nullptr;
}
FxExpression *x = new FxStateByIndex(ctx.StateIndex + i, ScriptPosition);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
return x;
}
else
{
FxExpression *x = new FxRuntimeStateIndex(basex);
x = x->Resolve(ctx);
basex = nullptr;
delete this;
return x;
}
}
}
else if (ValueType->isClassPointer())
{
FxExpression *x = new FxClassTypeCast(static_cast<PClassPointer*>(ValueType), basex, Explicit);
@ -2819,18 +2753,20 @@ FxExpression *FxAddSub::Resolve(FCompileContext& ctx)
return nullptr;
}
if (compileEnvironment.CheckForCustomAddition)
{
auto result = compileEnvironment.CheckForCustomAddition(this, ctx);
if (result)
{
ABORT(right);
goto goon;
}
}
if (left->ValueType == TypeTextureID && right->IsInteger())
{
ValueType = TypeTextureID;
}
else if (left->ValueType == TypeState && right->IsInteger() && Operator == '+' && !left->isConstant())
{
// This is the only special case of pointer addition that will be accepted - because it is used quite often in the existing game code.
ValueType = TypeState;
right = new FxMulDiv('*', right, new FxConstant((int)sizeof(FState), ScriptPosition)); // multiply by size here, so that constants can be better optimized.
right = right->Resolve(ctx);
ABORT(right);
}
else if (left->IsVector() && right->IsVector())
{
// a vector2 can be added to or subtracted from a vector 3 but it needs to be the right operand.
@ -2852,7 +2788,7 @@ FxExpression *FxAddSub::Resolve(FCompileContext& ctx)
// To check: It may be that this could pass in DECORATE, although setting TypeVoid here would pretty much prevent that.
goto error;
}
goon:
if (left->isConstant() && right->isConstant())
{
if (IsFloat())
@ -5151,11 +5087,9 @@ FxExpression *FxNew::Resolve(FCompileContext &ctx)
//==========================================================================
//
// The CVAR is for finding places where thinkers are created.
// Those will require code changes in ZScript 4.0.
//
//
//==========================================================================
CVAR(Bool, vm_warnthinkercreation, false, 0)
static DObject *BuiltinNew(PClass *cls, int outerside, int backwardscompatible)
{
@ -5174,28 +5108,9 @@ static DObject *BuiltinNew(PClass *cls, int outerside, int backwardscompatible)
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
return nullptr;
}
// Creating actors here must be outright prohibited,
if (cls->IsDescendantOf(NAME_Actor))
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
return nullptr;
}
if ((vm_warnthinkercreation || !backwardscompatible) && cls->IsDescendantOf(NAME_Thinker))
{
// This must output a diagnostic warning
Printf("Using 'new' to create thinkers is deprecated.");
}
// [ZZ] validate readonly and between scope construction
if (outerside) FScopeBarrier::ValidateNew(cls, outerside - 1);
DObject *object;
if (!cls->IsDescendantOf(NAME_Thinker))
{
object = cls->CreateNew();
}
else
{
object = currentVMLevel->CreateThinker(cls);
}
DObject *object = cls->CreateNew();
return object;
}
@ -5214,7 +5129,7 @@ ExpEmit FxNew::Emit(VMFunctionBuilder *build)
// Call DecoRandom to generate a random number.
VMFunction *callfunc;
auto sym = FindBuiltinFunction(NAME_BuiltinNew);
auto sym = FindBuiltinFunction(compileEnvironment.CustomBuiltinNew != NAME_None? compileEnvironment.CustomBuiltinNew : NAME_BuiltinNew);
assert(sym);
callfunc = sym->Variants[0].Implementation;
@ -5979,7 +5894,6 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
{
PSymbol * sym;
FxExpression *newex = nullptr;
int num;
CHECKRESOLVED();
@ -6007,31 +5921,12 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
}
}
if (Identifier == NAME_Default)
if (compileEnvironment.CheckSpecialIdentifier)
{
if (ctx.Function == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to access class defaults from constant declaration");
delete this;
return nullptr;
}
if (ctx.Function->Variants[0].SelfClass == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to access class defaults from static function");
delete this;
return nullptr;
}
if (!isActor(ctx.Function->Variants[0].SelfClass))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type.");
delete this;
return nullptr;
auto result = compileEnvironment.CheckSpecialIdentifier(this, ctx);
if (result != this) return result;
}
FxExpression * x = new FxClassDefaults(new FxSelf(ScriptPosition), ScriptPosition);
delete this;
return x->Resolve(ctx);
}
// Ugh, the horror. Constants need to be taken from the owning class, but members from the self class to catch invalid accesses here...
// see if the current class (if valid) defines something with this name.
@ -6164,14 +6059,6 @@ FxExpression *FxIdentifier::Resolve(FCompileContext& ctx)
}
}
// and line specials
if (newex == nullptr && (num = P_FindLineSpecial(Identifier.GetChars(), nullptr, nullptr)))
{
ScriptPosition.Message(MSG_DEBUGLOG, "Resolving name '%s' as line special %d\n", Identifier.GetChars(), num);
newex = new FxConstant(num, ScriptPosition);
goto foundit;
}
if (auto *cvar = FindCVar(Identifier.GetChars(), nullptr))
{
if (cvar->GetFlags() & CVAR_USERINFO)
@ -6205,21 +6092,12 @@ FxExpression *FxIdentifier::ResolveMember(FCompileContext &ctx, PContainerType *
PSymbolTable *symtbl;
bool isclass = objtype->isClass();
if (Identifier == NAME_Default)
if (compileEnvironment.ResolveSpecialIdentifier)
{
if (!isActor(objtype))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type.");
delete object;
object = nullptr;
return nullptr;
auto result = compileEnvironment.ResolveSpecialIdentifier(this, object, objtype, ctx);
if (result != this) return result;
}
FxExpression * x = new FxClassDefaults(object, ScriptPosition);
object = nullptr;
delete this;
return x->Resolve(ctx);
}
if (objtype != nullptr && (sym = objtype->Symbols.FindSymbolInTable(Identifier, symtbl)) != nullptr)
{
@ -6668,56 +6546,6 @@ FxExpression *FxSuper::Resolve(FCompileContext& ctx)
//
//==========================================================================
FxClassDefaults::FxClassDefaults(FxExpression *X, const FScriptPosition &pos)
: FxExpression(EFX_ClassDefaults, pos)
{
obj = X;
}
FxClassDefaults::~FxClassDefaults()
{
SAFE_DELETE(obj);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxClassDefaults::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(obj, ctx);
assert(obj->ValueType->isRealPointer());
ValueType = NewPointer(obj->ValueType->toPointer()->PointedType, true);
return this;
}
//==========================================================================
//
//
//
//==========================================================================
ExpEmit FxClassDefaults::Emit(VMFunctionBuilder *build)
{
ExpEmit ob = obj->Emit(build);
ob.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_CLSS, meta.RegNum, ob.RegNum);
build->Emit(OP_LP, meta.RegNum, meta.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
return meta;
}
//==========================================================================
//
//
//
//==========================================================================
FxGlobalVariable::FxGlobalVariable(PField* mem, const FScriptPosition &pos)
: FxMemberBase(EFX_GlobalVariable, mem, pos)
{
@ -7060,19 +6888,10 @@ FxExpression *FxStructMember::Resolve(FCompileContext &ctx)
CHECKRESOLVED();
SAFE_RESOLVE(classx, ctx);
if (membervar->SymbolName == NAME_Default)
if (compileEnvironment.CheckSpecialMember)
{
if (!classx->ValueType->isObjectPointer()
|| !static_cast<PObjectPointer *>(classx->ValueType)->PointedClass()->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type");
delete this;
return nullptr;
}
FxExpression * x = new FxClassDefaults(classx, ScriptPosition);
classx = nullptr;
delete this;
return x->Resolve(ctx);
auto result = compileEnvironment.CheckSpecialMember(this, ctx);
if (result != this) return result;
}
// [ZZ] support magic
@ -7710,7 +7529,7 @@ FxFunctionCall::~FxFunctionCall()
//
//==========================================================================
static bool CheckArgSize(FName fname, FArgumentList &args, int min, int max, FScriptPosition &sc)
bool CheckArgSize(FName fname, FArgumentList &args, int min, int max, FScriptPosition &sc)
{
int s = args.Size();
if (s < min)
@ -7853,46 +7672,10 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
}
}
int min, max, special;
if (MethodName == NAME_ACS_NamedExecuteWithResult || MethodName == NAME_CallACS)
if (compileEnvironment.CheckCustomGlobalFunctions)
{
special = -ACS_ExecuteWithResult;
min = 1;
max = 5;
}
else
{
// This alias is needed because Actor has a Teleport function.
if (MethodName == NAME_TeleportSpecial) MethodName = NAME_Teleport;
special = P_FindLineSpecial(MethodName.GetChars(), &min, &max);
}
if (special != 0 && min >= 0)
{
int paramcount = ArgList.Size();
if (ctx.Function == nullptr || ctx.Class == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to call action special %s from constant declaration", MethodName.GetChars());
delete this;
return nullptr;
}
else if (paramcount < min)
{
ScriptPosition.Message(MSG_ERROR, "Not enough parameters for '%s' (expected %d, got %d)",
MethodName.GetChars(), min, paramcount);
delete this;
return nullptr;
}
else if (paramcount > max)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters for '%s' (expected %d, got %d)",
MethodName.GetChars(), max, paramcount);
delete this;
return nullptr;
}
FxExpression *self = (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_Method) && isActor(ctx.Class)) ? new FxSelf(ScriptPosition) : (FxExpression*)new FxConstant(ScriptPosition);
FxExpression *x = new FxActionSpecialCall(self, special, ArgList, ScriptPosition);
delete this;
return x->Resolve(ctx);
auto result = compileEnvironment.CheckCustomGlobalFunctions(this, ctx);
if (result != this) return result;
}
PClass *cls = FindClassType(MethodName, ctx);
@ -7976,14 +7759,6 @@ FxExpression *FxFunctionCall::Resolve(FCompileContext& ctx)
}
break;
case NAME_GetDefaultByType:
if (CheckArgSize(NAME_GetDefaultByType, ArgList, 1, 1, ScriptPosition))
{
func = new FxGetDefaultByType(ArgList[0]);
ArgList[0] = nullptr;
}
break;
case NAME_SetRandomSeed:
if (CheckArgSize(NAME_Random, ArgList, 1, 1, ScriptPosition))
{
@ -8640,176 +8415,6 @@ isresolved:
}
//==========================================================================
//
// FxActionSpecialCall
//
// If special is negative, then the first argument will be treated as a
// name for ACS_NamedExecuteWithResult.
//
//==========================================================================
FxActionSpecialCall::FxActionSpecialCall(FxExpression *self, int special, FArgumentList &args, const FScriptPosition &pos)
: FxExpression(EFX_ActionSpecialCall, pos)
{
Self = self;
Special = special;
ArgList = std::move(args);
while (ArgList.Size() < 5)
{
ArgList.Push(new FxConstant(0, ScriptPosition));
}
}
//==========================================================================
//
//
//
//==========================================================================
FxActionSpecialCall::~FxActionSpecialCall()
{
SAFE_DELETE(Self);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxActionSpecialCall::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
bool failed = false;
SAFE_RESOLVE_OPT(Self, ctx);
for (unsigned i = 0; i < ArgList.Size(); i++)
{
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
failed = true;
}
else if (Special < 0 && i == 0)
{
if (ArgList[i]->ValueType == TypeString)
{
ArgList[i] = new FxNameCast(ArgList[i]);
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
failed = true;
}
}
else if (ArgList[i]->ValueType != TypeName)
{
ScriptPosition.Message(MSG_ERROR, "Name expected for parameter %d", i);
failed = true;
}
}
else if (!ArgList[i]->IsInteger())
{
if (ArgList[i]->ValueType->GetRegType() == REGT_FLOAT /* lax */)
{
ArgList[i] = new FxIntCast(ArgList[i], ctx.FromDecorate);
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
delete this;
return nullptr;
}
}
else
{
ScriptPosition.Message(MSG_ERROR, "Integer expected for parameter %d", i);
failed = true;
}
}
}
if (failed)
{
delete this;
return nullptr;
}
ValueType = TypeSInt32;
return this;
}
//==========================================================================
//
//
//
//==========================================================================
int BuiltinCallLineSpecial(int special, AActor *activator, int arg1, int arg2, int arg3, int arg4, int arg5)
{
return P_ExecuteSpecial(currentVMLevel , special, nullptr, activator, 0, arg1, arg2, arg3, arg4, arg5);
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinCallLineSpecial, BuiltinCallLineSpecial)
{
PARAM_PROLOGUE;
PARAM_INT(special);
PARAM_OBJECT(activator, AActor);
PARAM_INT(arg1);
PARAM_INT(arg2);
PARAM_INT(arg3);
PARAM_INT(arg4);
PARAM_INT(arg5);
ACTION_RETURN_INT(P_ExecuteSpecial(currentVMLevel, special, nullptr, activator, 0, arg1, arg2, arg3, arg4, arg5));
}
ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
{
unsigned i = 0;
// Call the BuiltinCallLineSpecial function to perform the desired special.
static uint8_t reginfo[] = { REGT_INT, REGT_POINTER, REGT_INT, REGT_INT, REGT_INT, REGT_INT, REGT_INT };
auto sym = FindBuiltinFunction(NAME_BuiltinCallLineSpecial);
assert(sym);
auto callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameterIntConst(abs(Special)); // pass special number
emitters.AddParameter(build, Self);
for (; i < ArgList.Size(); ++i)
{
FxExpression *argex = ArgList[i];
if (Special < 0 && i == 0)
{
assert(argex->ValueType == TypeName);
assert(argex->isConstant());
emitters.AddParameterIntConst(-static_cast<FxConstant *>(argex)->GetValue().GetName().GetIndex());
}
else
{
assert(argex->ValueType->GetRegType() == REGT_INT);
if (argex->isConstant())
{
emitters.AddParameterIntConst(static_cast<FxConstant *>(argex)->GetValue().GetInt());
}
else
{
emitters.AddParameter(build, argex);
}
}
}
ArgList.DeleteAndClear();
ArgList.ShrinkToFit();
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
}
//==========================================================================
//
// FxVMFunctionCall
@ -8901,41 +8506,6 @@ VMFunction *FxVMFunctionCall::GetDirectFunction(PFunction *callingfunc, const Ve
return nullptr;
}
//==========================================================================
//
// FxVMFunctionCall :: UnravelVarArgAJump
//
// Converts A_Jump(chance, a, b, c, d) -> A_Jump(chance, RandomPick[cajump](a, b, c, d))
// so that varargs are restricted to either text formatting or graphics drawing.
//
//==========================================================================
extern FRandom pr_cajump;
bool FxVMFunctionCall::UnravelVarArgAJump(FCompileContext &ctx)
{
FArgumentList rplist;
for (unsigned i = 1; i < ArgList.Size(); i++)
{
// This needs a bit of casting voodoo because RandomPick wants integer parameters.
auto x = new FxIntCast(new FxTypeCast(ArgList[i], TypeStateLabel, true, true), true, true);
rplist.Push(x->Resolve(ctx));
ArgList[i] = nullptr;
if (rplist[i - 1] == nullptr)
{
return false;
}
}
FxExpression *x = new FxRandomPick(&pr_cajump, rplist, false, ScriptPosition, true);
x = x->Resolve(ctx);
// This cannot be done with a cast because that interprets the value as an index.
// All we want here is to take the literal value and change its type.
if (x) x->ValueType = TypeStateLabel;
ArgList[1] = x;
ArgList.Clamp(2);
return x != nullptr;
}
//==========================================================================
//
// FxVMFunctionCall :: Resolve
@ -8968,20 +8538,13 @@ FxExpression *FxVMFunctionCall::Resolve(FCompileContext& ctx)
return nullptr;
}
// Unfortunately the PrintableName is the only safe thing to catch this special case here.
if (Function->Variants[0].Implementation->PrintableName.CompareNoCase("Actor.A_Jump [Native]") == 0)
if (compileEnvironment.ResolveSpecialFunction)
{
// Unravel the varargs part of this function here so that the VM->native interface does not have to deal with it anymore.
if (ArgList.Size() > 2)
{
auto ret = UnravelVarArgAJump(ctx);
if (!ret)
{
return nullptr;
}
}
auto result = compileEnvironment.ResolveSpecialFunction(this, ctx);
if (!result) return nullptr;
}
CallingFunction = ctx.Function;
if (ArgList.Size() > 0)
{
@ -9318,7 +8881,7 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
ArgList.ShrinkToFit();
if (!staticcall) emitters.SetVirtualReg(selfemit.RegNum);
int resultcount = vmfunc->Proto->ReturnTypes.Size() == 0 ? 0 : MAX(AssignCount, 1);
int resultcount = vmfunc->Proto->ReturnTypes.Size() == 0 ? 0 : std::max(AssignCount, 1);
assert((unsigned)resultcount <= vmfunc->Proto->ReturnTypes.Size());
for (int i = 0; i < resultcount; i++)
@ -9680,78 +9243,6 @@ ExpEmit FxGetClassName::Emit(VMFunctionBuilder *build)
//
//==========================================================================
FxGetDefaultByType::FxGetDefaultByType(FxExpression *self)
:FxExpression(EFX_GetDefaultByType, self->ScriptPosition)
{
Self = self;
}
FxGetDefaultByType::~FxGetDefaultByType()
{
SAFE_DELETE(Self);
}
FxExpression *FxGetDefaultByType::Resolve(FCompileContext &ctx)
{
SAFE_RESOLVE(Self, ctx);
PClass *cls = nullptr;
if (Self->ValueType == TypeString || Self->ValueType == TypeName)
{
if (Self->isConstant())
{
cls = PClass::FindActor(static_cast<FxConstant *>(Self)->GetValue().GetName());
if (cls == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type, but got %s", static_cast<FxConstant *>(Self)->GetValue().GetString().GetChars());
delete this;
return nullptr;
}
Self = new FxConstant(cls, NewClassPointer(cls), ScriptPosition);
}
else
{
// this is the ugly case. We do not know what we have and cannot do proper type casting.
// For now error out and let this case require explicit handling on the user side.
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type, but got %s", static_cast<FxConstant *>(Self)->GetValue().GetString().GetChars());
delete this;
return nullptr;
}
}
else
{
auto cp = PType::toClassPointer(Self->ValueType);
if (cp == nullptr || !cp->ClassRestriction->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type");
delete this;
return nullptr;
}
cls = cp->ClassRestriction;
}
ValueType = NewPointer(cls, true);
return this;
}
ExpEmit FxGetDefaultByType::Emit(VMFunctionBuilder *build)
{
ExpEmit op = Self->Emit(build);
op.Free(build);
ExpEmit to(build, REGT_POINTER);
if (op.Konst)
{
build->Emit(OP_LKP, to.RegNum, op.RegNum);
op = to;
}
build->Emit(OP_LP, to.RegNum, op.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
return to;
}
//==========================================================================
//
//
//==========================================================================
FxColorLiteral::FxColorLiteral(FArgumentList &args, FScriptPosition &sc)
:FxExpression(EFX_ColorLiteral, sc)
{
@ -11181,215 +10672,6 @@ ExpEmit FxClassPtrCast::Emit(VMFunctionBuilder *build)
return emitters.EmitCall(build);
}
//==========================================================================
//
// Symbolic state labels.
// Conversion will not happen inside the compiler anymore because it causes
// just too many problems.
//
//==========================================================================
FxExpression *FxStateByIndex::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
ABORT(ctx.Class);
auto vclass = PType::toClass(ctx.Class);
assert(vclass != nullptr);
auto aclass = ValidateActor(vclass->Descriptor);
// This expression type can only be used from actors, for everything else it has already produced a compile error.
assert(aclass != nullptr && aclass->GetStateCount() > 0);
if (aclass->GetStateCount() <= index)
{
ScriptPosition.Message(MSG_ERROR, "%s: Attempt to jump to non existing state index %d",
ctx.Class->TypeName.GetChars(), index);
delete this;
return nullptr;
}
int symlabel = StateLabels.AddPointer(aclass->GetStates() + index);
FxExpression *x = new FxConstant(symlabel, ScriptPosition);
x->ValueType = TypeStateLabel;
delete this;
return x;
}
//==========================================================================
//
//
//
//==========================================================================
FxRuntimeStateIndex::FxRuntimeStateIndex(FxExpression *index)
: FxExpression(EFX_RuntimeStateIndex, index->ScriptPosition), Index(index)
{
ValueType = TypeStateLabel;
}
FxRuntimeStateIndex::~FxRuntimeStateIndex()
{
SAFE_DELETE(Index);
}
FxExpression *FxRuntimeStateIndex::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(Index, ctx);
if (!Index->IsNumeric())
{
ScriptPosition.Message(MSG_ERROR, "Numeric type expected");
delete this;
return nullptr;
}
else if (Index->isConstant())
{
int index = static_cast<FxConstant *>(Index)->GetValue().GetInt();
if (index < 0 || (index == 0 && !ctx.FromDecorate))
{
ScriptPosition.Message(MSG_ERROR, "State index must be positive");
delete this;
return nullptr;
}
else if (index == 0)
{
int symlabel = StateLabels.AddPointer(nullptr);
auto x = new FxConstant(symlabel, ScriptPosition);
delete this;
x->ValueType = TypeStateLabel;
return x;
}
else
{
auto x = new FxStateByIndex(ctx.StateIndex + index, ScriptPosition);
delete this;
return x->Resolve(ctx);
}
}
else if (Index->ValueType->GetRegType() != REGT_INT)
{ // Float.
Index = new FxIntCast(Index, ctx.FromDecorate);
SAFE_RESOLVE(Index, ctx);
}
auto vclass = PType::toClass(ctx.Class);
assert(vclass != nullptr);
auto aclass = ValidateActor(vclass->Descriptor);
assert(aclass != nullptr && aclass->GetStateCount() > 0);
symlabel = StateLabels.AddPointer(aclass->GetStates() + ctx.StateIndex);
ValueType = TypeStateLabel;
return this;
}
ExpEmit FxRuntimeStateIndex::Emit(VMFunctionBuilder *build)
{
ExpEmit out = Index->Emit(build);
// out = (clamp(Index, 0, 32767) << 16) | symlabel | 0x80000000; 0x80000000 is here to make it negative.
build->Emit(OP_MAX_RK, out.RegNum, out.RegNum, build->GetConstantInt(0));
build->Emit(OP_MIN_RK, out.RegNum, out.RegNum, build->GetConstantInt(32767));
build->Emit(OP_SLL_RI, out.RegNum, out.RegNum, 16);
build->Emit(OP_OR_RK, out.RegNum, out.RegNum, build->GetConstantInt(symlabel|0x80000000));
return out;
}
//==========================================================================
//
//
//
//==========================================================================
FxMultiNameState::FxMultiNameState(const char *_statestring, const FScriptPosition &pos, PClassActor *checkclass)
:FxExpression(EFX_MultiNameState, pos)
{
FName scopename = NAME_None;
FString statestring = _statestring;
int scopeindex = statestring.IndexOf("::");
if (scopeindex >= 0)
{
scopename = FName(statestring, scopeindex, false);
statestring = statestring.Right(statestring.Len() - scopeindex - 2);
}
names = MakeStateNameList(statestring);
names.Insert(0, scopename);
scope = checkclass;
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
ABORT(ctx.Class);
int symlabel;
auto vclass = PType::toClass(ctx.Class);
//assert(vclass != nullptr);
auto clstype = vclass == nullptr? nullptr : ValidateActor(vclass->Descriptor);
if (names[0] == NAME_None)
{
scope = nullptr;
}
else if (clstype == nullptr)
{
// not in an actor, so any further checks are pointless.
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(), ctx.Class->TypeName.GetChars());
delete this;
return nullptr;
}
else if (names[0] == NAME_Super)
{
scope = ValidateActor(clstype->ParentClass);
}
else
{
scope = PClass::FindActor(names[0]);
if (scope == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unknown class '%s' in state label", names[0].GetChars());
delete this;
return nullptr;
}
else if (!scope->IsAncestorOf(clstype))
{
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(), ctx.Class->TypeName.GetChars());
delete this;
return nullptr;
}
}
if (scope != nullptr)
{
FState *destination = nullptr;
// If the label is class specific we can resolve it right here
if (names[1] != NAME_None)
{
destination = scope->FindState(names.Size()-1, &names[1], false);
if (destination == nullptr)
{
ScriptPosition.Message(MSG_OPTERROR, "Unknown state jump destination");
/* lax */
return this;
}
}
symlabel = StateLabels.AddPointer(destination);
}
else
{
names.Delete(0);
symlabel = StateLabels.AddNames(names);
}
FxExpression *x = new FxConstant(symlabel, ScriptPosition);
x->ValueType = TypeStateLabel;
delete this;
return x;
}
//==========================================================================
//
// declares a single local variable (no arrays)

View file

@ -42,14 +42,14 @@
#include "m_random.h"
#include "sc_man.h"
#include "s_sound.h"
#include "actor.h"
#include "s_soundinternal.h"
#include "vmbuilder.h"
#include "scopebarrier.h"
#include "types.h"
#include "vmintern.h"
#include "c_cvars.h"
struct FState; // needed for FxConstant. Maybe move the state constructor to a subclass later?
#define CHECKRESOLVED() if (isresolved) return this; isresolved=true;
#define SAFE_DELETE(p) if (p!=NULL) { delete p; p=NULL; }
@ -398,23 +398,6 @@ public:
};
//==========================================================================
//
// FxClassDefaults
//
//==========================================================================
class FxClassDefaults : public FxExpression
{
FxExpression *obj;
public:
FxClassDefaults(FxExpression *, const FScriptPosition &);
~FxClassDefaults();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxConstant
@ -706,12 +689,11 @@ public:
class FxTypeCast : public FxExpression
{
public:
FxExpression *basex;
bool NoWarn;
bool Explicit;
public:
FxTypeCast(FxExpression *x, PType *type, bool nowarn, bool explicitly = false);
~FxTypeCast();
FxExpression *Resolve(FCompileContext&);
@ -1539,11 +1521,11 @@ public:
class FxFunctionCall : public FxExpression
{
FName MethodName;
FRandom *RNG;
FArgumentList ArgList;
public:
FName MethodName;
FArgumentList ArgList;
FxFunctionCall(FName methodname, FName rngname, FArgumentList &args, const FScriptPosition &pos);
~FxFunctionCall();
@ -1571,26 +1553,6 @@ public:
};
//==========================================================================
//
// FxActionSpecialCall
//
//==========================================================================
class FxActionSpecialCall : public FxExpression
{
int Special;
FxExpression *Self;
FArgumentList ArgList;
public:
FxActionSpecialCall(FxExpression *self, int special, FArgumentList &args, const FScriptPosition &pos);
~FxActionSpecialCall();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxFlopFunctionCall
@ -1701,24 +1663,6 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxGetDefaultByType
//
//==========================================================================
class FxGetDefaultByType : public FxExpression
{
FxExpression *Self;
public:
FxGetDefaultByType(FxExpression *self);
~FxGetDefaultByType();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxColorLiteral
@ -1750,17 +1694,18 @@ class FxVMFunctionCall : public FxExpression
bool NoVirtual;
bool hasStringArgs = false;
FxExpression *Self;
PFunction *Function;
FArgumentList ArgList;
// for multi assignment
int AssignCount = 0;
TArray<ExpEmit> ReturnRegs;
PFunction *CallingFunction;
bool CheckAccessibility(const VersionInfo &ver);
bool UnravelVarArgAJump(FCompileContext&);
public:
FArgumentList ArgList;
PFunction* Function;
FxVMFunctionCall(FxExpression *self, PFunction *func, FArgumentList &args, const FScriptPosition &pos, bool novirtual);
~FxVMFunctionCall();
FxExpression *Resolve(FCompileContext&);
@ -2041,60 +1986,6 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// Only used to resolve the old jump by index feature of DECORATE
//
//==========================================================================
class FxStateByIndex : public FxExpression
{
unsigned index;
public:
FxStateByIndex(int i, const FScriptPosition &pos) : FxExpression(EFX_StateByIndex, pos)
{
index = i;
}
FxExpression *Resolve(FCompileContext&);
};
//==========================================================================
//
// Same as above except for expressions which means it will have to be
// evaluated at runtime
//
//==========================================================================
class FxRuntimeStateIndex : public FxExpression
{
FxExpression *Index;
int symlabel;
public:
FxRuntimeStateIndex(FxExpression *index);
~FxRuntimeStateIndex();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
//
//
//==========================================================================
class FxMultiNameState : public FxExpression
{
PClassActor *scope;
TArray<FName> names;
public:
FxMultiNameState(const char *statestring, const FScriptPosition &pos, PClassActor *checkclass = nullptr);
FxExpression *Resolve(FCompileContext&);
};
//==========================================================================
//
//
@ -2234,4 +2125,19 @@ public:
ExpEmit Emit(VMFunctionBuilder *build);
};
struct CompileEnvironment
{
FxExpression* (*SpecialTypeCast)(FxTypeCast* func, FCompileContext& ctx);
bool (*CheckForCustomAddition)(FxAddSub* func, FCompileContext& ctx);
FxExpression* (*CheckSpecialIdentifier)(FxIdentifier* func, FCompileContext& ctx);
FxExpression* (*ResolveSpecialIdentifier)(FxIdentifier* func, FxExpression*& object, PContainerType* objtype, FCompileContext& ctx);
FxExpression* (*CheckSpecialMember)(FxStructMember* func, FCompileContext& ctx);
FxExpression* (*CheckCustomGlobalFunctions)(FxFunctionCall* func, FCompileContext& ctx);
bool (*ResolveSpecialFunction)(FxVMFunctionCall* func, FCompileContext& ctx);
FName CustomBuiltinNew; //override the 'new' function if some classes need special treatment.
};
extern CompileEnvironment compileEnvironment;
#endif

View file

@ -0,0 +1,961 @@
/*
** codegen.cpp
**
** Compiler backend / code generation for ZScript and DECORATE
**
**---------------------------------------------------------------------------
** Copyright 2008-2016 Christoph Oelckers
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in the
** documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
** derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
**
*/
#include <stdlib.h>
#include "cmdlib.h"
#include "codegen.h"
#include "codegen_doom.h"
#include "v_text.h"
#include "filesystem.h"
#include "v_video.h"
#include "utf8.h"
#include "texturemanager.h"
#include "m_random.h"
#include "v_font.h"
#include "templates.h"
#include "actor.h"
#include "p_lnspec.h"
#include "g_levellocals.h"
PFunction* FindBuiltinFunction(FName funcname);
//==========================================================================
//
//
//
//==========================================================================
bool isActor(PContainerType *type)
{
auto cls = PType::toClass(type);
return cls ? cls->Descriptor->IsDescendantOf(RUNTIME_CLASS(AActor)) : false;
}
//==========================================================================
//
//
//
//==========================================================================
static FxExpression *CustomTypeCast(FxTypeCast *func, FCompileContext &ctx)
{
if (func->ValueType == TypeStateLabel)
{
auto& basex = func->basex;
auto& ScriptPosition = func->ScriptPosition;
if (basex->ValueType == TypeNullPtr)
{
auto x = new FxConstant(0, ScriptPosition);
x->ValueType = TypeStateLabel;
delete func;
return x;
}
// Right now this only supports string constants. There should be an option to pass a string variable, too.
if (basex->isConstant() && (basex->ValueType == TypeString || basex->ValueType == TypeName))
{
FString s= static_cast<FxConstant *>(basex)->GetValue().GetString();
if (s.Len() == 0 && !ctx.FromDecorate) // DECORATE should never get here at all, but let's better be safe.
{
ScriptPosition.Message(MSG_ERROR, "State jump to empty label.");
delete func;
return nullptr;
}
FxExpression *x = new FxMultiNameState(s, basex->ScriptPosition);
x = x->Resolve(ctx);
basex = nullptr;
delete func;
return x;
}
else if (basex->IsNumeric() && basex->ValueType != TypeSound && basex->ValueType != TypeColor)
{
if (ctx.StateIndex < 0)
{
ScriptPosition.Message(MSG_ERROR, "State jumps with index can only be used in anonymous state functions.");
delete func;
return nullptr;
}
if (ctx.StateCount != 1)
{
ScriptPosition.Message(MSG_ERROR, "State jumps with index cannot be used on multistate definitions");
delete func;
return nullptr;
}
if (basex->isConstant())
{
int i = static_cast<FxConstant *>(basex)->GetValue().GetInt();
if (i <= 0)
{
ScriptPosition.Message(MSG_ERROR, "State index must be positive");
delete func;
return nullptr;
}
FxExpression *x = new FxStateByIndex(ctx.StateIndex + i, ScriptPosition);
x = x->Resolve(ctx);
basex = nullptr;
delete func;
return x;
}
else
{
FxExpression *x = new FxRuntimeStateIndex(basex);
x = x->Resolve(ctx);
basex = nullptr;
delete func;
return x;
}
}
}
return func;
}
//==========================================================================
//
//
//
//==========================================================================
static bool CheckForCustomAddition(FxAddSub *func, FCompileContext &ctx)
{
if (func->left->ValueType == TypeState && func->right->IsInteger() && func->Operator == '+' && !func->left->isConstant())
{
// This is the only special case of pointer addition that will be accepted - because it is used quite often in the existing game code.
func->ValueType = TypeState;
func->right = new FxMulDiv('*', func->right, new FxConstant((int)sizeof(FState), func->ScriptPosition)); // multiply by size here, so that constants can be better optimized.
func->right = func->right->Resolve(ctx);
return true;
}
return false;
}
//==========================================================================
//
//
//
//==========================================================================
static FxExpression* CheckForDefault(FxIdentifier *func, FCompileContext &ctx)
{
auto& ScriptPosition = func->ScriptPosition;
if (func->Identifier == NAME_Default)
{
if (ctx.Function == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to access class defaults from constant declaration");
delete func;
return nullptr;
}
if (ctx.Function->Variants[0].SelfClass == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to access class defaults from static function");
delete func;
return nullptr;
}
if (!isActor(ctx.Function->Variants[0].SelfClass))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type.");
delete func;
return nullptr;
}
FxExpression * x = new FxClassDefaults(new FxSelf(ScriptPosition), ScriptPosition);
delete func;
return x->Resolve(ctx);
}
// and line specials
int num;
if ((num = P_FindLineSpecial(func->Identifier.GetChars(), nullptr, nullptr)))
{
ScriptPosition.Message(MSG_DEBUGLOG, "Resolving name '%s' as line special %d\n", func->Identifier.GetChars(), num);
auto newex = new FxConstant(num, ScriptPosition);
delete func;
return newex? newex->Resolve(ctx) : nullptr;
}
return func;
}
//==========================================================================
//
//
//
//==========================================================================
static FxExpression *ResolveForDefault(FxIdentifier *expr, FxExpression*& object, PContainerType* objtype, FCompileContext &ctx)
{
if (expr->Identifier == NAME_Default)
{
if (!isActor(objtype))
{
expr->ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type.");
delete object;
object = nullptr;
return nullptr;
}
FxExpression * x = new FxClassDefaults(object, expr->ScriptPosition);
object = nullptr;
delete expr;
return x->Resolve(ctx);
}
return expr;
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression* CheckForMemberDefault(FxStructMember *func, FCompileContext &ctx)
{
auto& membervar = func->membervar;
auto& classx = func->classx;
auto& ScriptPosition = func->ScriptPosition;
if (membervar->SymbolName == NAME_Default)
{
if (!classx->ValueType->isObjectPointer()
|| !static_cast<PObjectPointer *>(classx->ValueType)->PointedClass()->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
ScriptPosition.Message(MSG_ERROR, "'Default' requires an actor type");
delete func;
return nullptr;
}
FxExpression * x = new FxClassDefaults(classx, ScriptPosition);
classx = nullptr;
delete func;
return x->Resolve(ctx);
}
return func;
}
//==========================================================================
//
// FxVMFunctionCall :: UnravelVarArgAJump
//
// Converts A_Jump(chance, a, b, c, d) -> A_Jump(chance, RandomPick[cajump](a, b, c, d))
// so that varargs are restricted to either text formatting or graphics drawing.
//
//==========================================================================
extern FRandom pr_cajump;
static bool UnravelVarArgAJump(FxVMFunctionCall *func, FCompileContext &ctx)
{
auto& ArgList = func->ArgList;
FArgumentList rplist;
for (unsigned i = 1; i < ArgList.Size(); i++)
{
// This needs a bit of casting voodoo because RandomPick wants integer parameters.
auto x = new FxIntCast(new FxTypeCast(ArgList[i], TypeStateLabel, true, true), true, true);
rplist.Push(x->Resolve(ctx));
ArgList[i] = nullptr;
if (rplist[i - 1] == nullptr)
{
return false;
}
}
FxExpression *x = new FxRandomPick(&pr_cajump, rplist, false, func->ScriptPosition, true);
x = x->Resolve(ctx);
// This cannot be done with a cast because that interprets the value as an index.
// All we want here is to take the literal value and change its type.
if (x) x->ValueType = TypeStateLabel;
ArgList[1] = x;
ArgList.Clamp(2);
return x != nullptr;
}
static bool AJumpProcessing(FxVMFunctionCall *func, FCompileContext &ctx)
{
// Unfortunately the PrintableName is the only safe thing to catch this special case here.
if (func->Function->Variants[0].Implementation->PrintableName.CompareNoCase("Actor.A_Jump [Native]") == 0)
{
// Unravel the varargs part of this function here so that the VM->native interface does not have to deal with it anymore.
if (func->ArgList.Size() > 2)
{
auto ret = UnravelVarArgAJump(func, ctx);
if (!ret)
{
return false;
}
}
}
return true;
}
//==========================================================================
//
//
//
//==========================================================================
bool CheckArgSize(FName fname, FArgumentList &args, int min, int max, FScriptPosition &sc);
static FxExpression *ResolveGlobalCustomFunction(FxFunctionCall *func, FCompileContext &ctx)
{
auto& ScriptPosition = func->ScriptPosition;
if (func->MethodName == NAME_GetDefaultByType)
{
if (CheckArgSize(NAME_GetDefaultByType, func->ArgList, 1, 1, ScriptPosition))
{
auto newfunc = new FxGetDefaultByType(func->ArgList[0]);
func->ArgList[0] = nullptr;
delete func;
return newfunc->Resolve(ctx);
}
}
int min, max, special;
if (func->MethodName == NAME_ACS_NamedExecuteWithResult || func->MethodName == NAME_CallACS)
{
special = -ACS_ExecuteWithResult;
min = 1;
max = 5;
}
else
{
// This alias is needed because Actor has a Teleport function.
if (func->MethodName == NAME_TeleportSpecial) func->MethodName = NAME_Teleport;
special = P_FindLineSpecial(func->MethodName.GetChars(), &min, &max);
}
if (special != 0 && min >= 0)
{
int paramcount = func->ArgList.Size();
if (ctx.Function == nullptr || ctx.Class == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unable to call action special %s from constant declaration", func->MethodName.GetChars());
delete func;
return nullptr;
}
else if (paramcount < min)
{
ScriptPosition.Message(MSG_ERROR, "Not enough parameters for '%s' (expected %d, got %d)",
func->MethodName.GetChars(), min, paramcount);
delete func;
return nullptr;
}
else if (paramcount > max)
{
ScriptPosition.Message(MSG_ERROR, "too many parameters for '%s' (expected %d, got %d)",
func->MethodName.GetChars(), max, paramcount);
delete func;
return nullptr;
}
FxExpression *self = (ctx.Function && (ctx.Function->Variants[0].Flags & VARF_Method) && isActor(ctx.Class)) ? new FxSelf(ScriptPosition) : (FxExpression*)new FxConstant(ScriptPosition);
FxExpression *x = new FxActionSpecialCall(self, special, func->ArgList, ScriptPosition);
delete func;
return x->Resolve(ctx);
}
return func;
}
//==========================================================================
//
// FxActionSpecialCall
//
// If special is negative, then the first argument will be treated as a
// name for ACS_NamedExecuteWithResult.
//
//==========================================================================
FxActionSpecialCall::FxActionSpecialCall(FxExpression *self, int special, FArgumentList &args, const FScriptPosition &pos)
: FxExpression(EFX_ActionSpecialCall, pos)
{
Self = self;
Special = special;
ArgList = std::move(args);
while (ArgList.Size() < 5)
{
ArgList.Push(new FxConstant(0, ScriptPosition));
}
}
//==========================================================================
//
//
//
//==========================================================================
FxActionSpecialCall::~FxActionSpecialCall()
{
SAFE_DELETE(Self);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxActionSpecialCall::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
bool failed = false;
SAFE_RESOLVE_OPT(Self, ctx);
for (unsigned i = 0; i < ArgList.Size(); i++)
{
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
failed = true;
}
else if (Special < 0 && i == 0)
{
if (ArgList[i]->ValueType == TypeString)
{
ArgList[i] = new FxNameCast(ArgList[i]);
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
failed = true;
}
}
else if (ArgList[i]->ValueType != TypeName)
{
ScriptPosition.Message(MSG_ERROR, "Name expected for parameter %d", i);
failed = true;
}
}
else if (!ArgList[i]->IsInteger())
{
if (ArgList[i]->ValueType->GetRegType() == REGT_FLOAT /* lax */)
{
ArgList[i] = new FxIntCast(ArgList[i], ctx.FromDecorate);
ArgList[i] = ArgList[i]->Resolve(ctx);
if (ArgList[i] == nullptr)
{
delete this;
return nullptr;
}
}
else
{
ScriptPosition.Message(MSG_ERROR, "Integer expected for parameter %d", i);
failed = true;
}
}
}
if (failed)
{
delete this;
return nullptr;
}
ValueType = TypeSInt32;
return this;
}
//==========================================================================
//
//
//
//==========================================================================
int BuiltinCallLineSpecial(int special, AActor *activator, int arg1, int arg2, int arg3, int arg4, int arg5)
{
return P_ExecuteSpecial(currentVMLevel , special, nullptr, activator, 0, arg1, arg2, arg3, arg4, arg5);
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinCallLineSpecial, BuiltinCallLineSpecial)
{
PARAM_PROLOGUE;
PARAM_INT(special);
PARAM_OBJECT(activator, AActor);
PARAM_INT(arg1);
PARAM_INT(arg2);
PARAM_INT(arg3);
PARAM_INT(arg4);
PARAM_INT(arg5);
ACTION_RETURN_INT(P_ExecuteSpecial(currentVMLevel, special, nullptr, activator, 0, arg1, arg2, arg3, arg4, arg5));
}
ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
{
unsigned i = 0;
// Call the BuiltinCallLineSpecial function to perform the desired special.
static uint8_t reginfo[] = { REGT_INT, REGT_POINTER, REGT_INT, REGT_INT, REGT_INT, REGT_INT, REGT_INT };
auto sym = FindBuiltinFunction(NAME_BuiltinCallLineSpecial);
assert(sym);
auto callfunc = sym->Variants[0].Implementation;
FunctionCallEmitter emitters(callfunc);
emitters.AddParameterIntConst(abs(Special)); // pass special number
emitters.AddParameter(build, Self);
for (; i < ArgList.Size(); ++i)
{
FxExpression *argex = ArgList[i];
if (Special < 0 && i == 0)
{
assert(argex->ValueType == TypeName);
assert(argex->isConstant());
emitters.AddParameterIntConst(-static_cast<FxConstant *>(argex)->GetValue().GetName().GetIndex());
}
else
{
assert(argex->ValueType->GetRegType() == REGT_INT);
if (argex->isConstant())
{
emitters.AddParameterIntConst(static_cast<FxConstant *>(argex)->GetValue().GetInt());
}
else
{
emitters.AddParameter(build, argex);
}
}
}
ArgList.DeleteAndClear();
ArgList.ShrinkToFit();
emitters.AddReturn(REGT_INT);
return emitters.EmitCall(build);
}
//==========================================================================
//
//
//
//==========================================================================
FxClassDefaults::FxClassDefaults(FxExpression *X, const FScriptPosition &pos)
: FxExpression(EFX_ClassDefaults, pos)
{
obj = X;
}
FxClassDefaults::~FxClassDefaults()
{
SAFE_DELETE(obj);
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxClassDefaults::Resolve(FCompileContext& ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(obj, ctx);
assert(obj->ValueType->isRealPointer());
ValueType = NewPointer(obj->ValueType->toPointer()->PointedType, true);
return this;
}
//==========================================================================
//
//
//
//==========================================================================
ExpEmit FxClassDefaults::Emit(VMFunctionBuilder *build)
{
ExpEmit ob = obj->Emit(build);
ob.Free(build);
ExpEmit meta(build, REGT_POINTER);
build->Emit(OP_CLSS, meta.RegNum, ob.RegNum);
build->Emit(OP_LP, meta.RegNum, meta.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
return meta;
}
//==========================================================================
//
//
//==========================================================================
FxGetDefaultByType::FxGetDefaultByType(FxExpression *self)
:FxExpression(EFX_GetDefaultByType, self->ScriptPosition)
{
Self = self;
}
FxGetDefaultByType::~FxGetDefaultByType()
{
SAFE_DELETE(Self);
}
FxExpression *FxGetDefaultByType::Resolve(FCompileContext &ctx)
{
SAFE_RESOLVE(Self, ctx);
PClass *cls = nullptr;
if (Self->ValueType == TypeString || Self->ValueType == TypeName)
{
if (Self->isConstant())
{
cls = PClass::FindActor(static_cast<FxConstant *>(Self)->GetValue().GetName());
if (cls == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type, but got %s", static_cast<FxConstant *>(Self)->GetValue().GetString().GetChars());
delete this;
return nullptr;
}
Self = new FxConstant(cls, NewClassPointer(cls), ScriptPosition);
}
else
{
// this is the ugly case. We do not know what we have and cannot do proper type casting.
// For now error out and let this case require explicit handling on the user side.
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type, but got %s", static_cast<FxConstant *>(Self)->GetValue().GetString().GetChars());
delete this;
return nullptr;
}
}
else
{
auto cp = PType::toClassPointer(Self->ValueType);
if (cp == nullptr || !cp->ClassRestriction->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
ScriptPosition.Message(MSG_ERROR, "GetDefaultByType() requires an actor class type");
delete this;
return nullptr;
}
cls = cp->ClassRestriction;
}
ValueType = NewPointer(cls, true);
return this;
}
ExpEmit FxGetDefaultByType::Emit(VMFunctionBuilder *build)
{
ExpEmit op = Self->Emit(build);
op.Free(build);
ExpEmit to(build, REGT_POINTER);
if (op.Konst)
{
build->Emit(OP_LKP, to.RegNum, op.RegNum);
op = to;
}
build->Emit(OP_LP, to.RegNum, op.RegNum, build->GetConstantInt(myoffsetof(PClass, Defaults)));
return to;
}
//==========================================================================
//
// Symbolic state labels.
// Conversion will not happen inside the compiler anymore because it causes
// just too many problems.
//
//==========================================================================
FxExpression *FxStateByIndex::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
ABORT(ctx.Class);
auto vclass = PType::toClass(ctx.Class);
assert(vclass != nullptr);
auto aclass = ValidateActor(vclass->Descriptor);
// This expression type can only be used from actors, for everything else it has already produced a compile error.
assert(aclass != nullptr && aclass->GetStateCount() > 0);
if (aclass->GetStateCount() <= index)
{
ScriptPosition.Message(MSG_ERROR, "%s: Attempt to jump to non existing state index %d",
ctx.Class->TypeName.GetChars(), index);
delete this;
return nullptr;
}
int symlabel = StateLabels.AddPointer(aclass->GetStates() + index);
FxExpression *x = new FxConstant(symlabel, ScriptPosition);
x->ValueType = TypeStateLabel;
delete this;
return x;
}
//==========================================================================
//
//
//
//==========================================================================
FxRuntimeStateIndex::FxRuntimeStateIndex(FxExpression *index)
: FxExpression(EFX_RuntimeStateIndex, index->ScriptPosition), Index(index)
{
ValueType = TypeStateLabel;
}
FxRuntimeStateIndex::~FxRuntimeStateIndex()
{
SAFE_DELETE(Index);
}
FxExpression *FxRuntimeStateIndex::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
SAFE_RESOLVE(Index, ctx);
if (!Index->IsNumeric())
{
ScriptPosition.Message(MSG_ERROR, "Numeric type expected");
delete this;
return nullptr;
}
else if (Index->isConstant())
{
int index = static_cast<FxConstant *>(Index)->GetValue().GetInt();
if (index < 0 || (index == 0 && !ctx.FromDecorate))
{
ScriptPosition.Message(MSG_ERROR, "State index must be positive");
delete this;
return nullptr;
}
else if (index == 0)
{
int symlabel = StateLabels.AddPointer(nullptr);
auto x = new FxConstant(symlabel, ScriptPosition);
delete this;
x->ValueType = TypeStateLabel;
return x;
}
else
{
auto x = new FxStateByIndex(ctx.StateIndex + index, ScriptPosition);
delete this;
return x->Resolve(ctx);
}
}
else if (Index->ValueType->GetRegType() != REGT_INT)
{ // Float.
Index = new FxIntCast(Index, ctx.FromDecorate);
SAFE_RESOLVE(Index, ctx);
}
auto vclass = PType::toClass(ctx.Class);
assert(vclass != nullptr);
auto aclass = ValidateActor(vclass->Descriptor);
assert(aclass != nullptr && aclass->GetStateCount() > 0);
symlabel = StateLabels.AddPointer(aclass->GetStates() + ctx.StateIndex);
ValueType = TypeStateLabel;
return this;
}
ExpEmit FxRuntimeStateIndex::Emit(VMFunctionBuilder *build)
{
ExpEmit out = Index->Emit(build);
// out = (clamp(Index, 0, 32767) << 16) | symlabel | 0x80000000; 0x80000000 is here to make it negative.
build->Emit(OP_MAX_RK, out.RegNum, out.RegNum, build->GetConstantInt(0));
build->Emit(OP_MIN_RK, out.RegNum, out.RegNum, build->GetConstantInt(32767));
build->Emit(OP_SLL_RI, out.RegNum, out.RegNum, 16);
build->Emit(OP_OR_RK, out.RegNum, out.RegNum, build->GetConstantInt(symlabel|0x80000000));
return out;
}
//==========================================================================
//
//
//
//==========================================================================
FxMultiNameState::FxMultiNameState(const char *_statestring, const FScriptPosition &pos, PClassActor *checkclass)
:FxExpression(EFX_MultiNameState, pos)
{
FName scopename = NAME_None;
FString statestring = _statestring;
int scopeindex = statestring.IndexOf("::");
if (scopeindex >= 0)
{
scopename = FName(statestring, scopeindex, false);
statestring = statestring.Right(statestring.Len() - scopeindex - 2);
}
names = MakeStateNameList(statestring);
names.Insert(0, scopename);
scope = checkclass;
}
//==========================================================================
//
//
//
//==========================================================================
FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx)
{
CHECKRESOLVED();
ABORT(ctx.Class);
int symlabel;
auto vclass = PType::toClass(ctx.Class);
//assert(vclass != nullptr);
auto clstype = vclass == nullptr? nullptr : ValidateActor(vclass->Descriptor);
if (names[0] == NAME_None)
{
scope = nullptr;
}
else if (clstype == nullptr)
{
// not in an actor, so any further checks are pointless.
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(), ctx.Class->TypeName.GetChars());
delete this;
return nullptr;
}
else if (names[0] == NAME_Super)
{
scope = ValidateActor(clstype->ParentClass);
}
else
{
scope = PClass::FindActor(names[0]);
if (scope == nullptr)
{
ScriptPosition.Message(MSG_ERROR, "Unknown class '%s' in state label", names[0].GetChars());
delete this;
return nullptr;
}
else if (!scope->IsAncestorOf(clstype))
{
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(), ctx.Class->TypeName.GetChars());
delete this;
return nullptr;
}
}
if (scope != nullptr)
{
FState *destination = nullptr;
// If the label is class specific we can resolve it right here
if (names[1] != NAME_None)
{
destination = scope->FindState(names.Size()-1, &names[1], false);
if (destination == nullptr)
{
ScriptPosition.Message(MSG_OPTERROR, "Unknown state jump destination");
/* lax */
return this;
}
}
symlabel = StateLabels.AddPointer(destination);
}
else
{
names.Delete(0);
symlabel = StateLabels.AddNames(names);
}
FxExpression *x = new FxConstant(symlabel, ScriptPosition);
x->ValueType = TypeStateLabel;
delete this;
return x;
}
//==========================================================================
//
// The CVAR is for finding places where thinkers are created.
// Those will require code changes in ZScript 4.0.
//
//==========================================================================
CVAR(Bool, vm_warnthinkercreation, false, 0)
static DObject *BuiltinNewDoom(PClass *cls, int outerside, int backwardscompatible)
{
if (cls == nullptr)
{
ThrowAbortException(X_OTHER, "New without a class");
return nullptr;
}
if (cls->ConstructNative == nullptr)
{
ThrowAbortException(X_OTHER, "Class %s requires native construction", cls->TypeName.GetChars());
return nullptr;
}
if (cls->bAbstract)
{
ThrowAbortException(X_OTHER, "Cannot instantiate abstract class %s", cls->TypeName.GetChars());
return nullptr;
}
// Creating actors here must be outright prohibited,
if (cls->IsDescendantOf(NAME_Actor))
{
ThrowAbortException(X_OTHER, "Cannot create actors with 'new'");
return nullptr;
}
if ((vm_warnthinkercreation || !backwardscompatible) && cls->IsDescendantOf(NAME_Thinker))
{
// This must output a diagnostic warning
Printf("Using 'new' to create thinkers is deprecated.");
}
// [ZZ] validate readonly and between scope construction
if (outerside) FScopeBarrier::ValidateNew(cls, outerside - 1);
DObject *object;
if (!cls->IsDescendantOf(NAME_Thinker))
{
object = cls->CreateNew();
}
else
{
object = currentVMLevel->CreateThinker(cls);
}
return object;
}
DEFINE_ACTION_FUNCTION_NATIVE(DObject, BuiltinNewDoom, BuiltinNewDoom)
{
PARAM_PROLOGUE;
PARAM_CLASS(cls, DObject);
PARAM_INT(outerside);
PARAM_INT(compatible);
ACTION_RETURN_OBJECT(BuiltinNewDoom(cls, outerside, compatible));
}
void SetDoomCompileEnvironment()
{
compileEnvironment.SpecialTypeCast = CustomTypeCast;
compileEnvironment.CheckForCustomAddition = CheckForCustomAddition;
compileEnvironment.CheckSpecialIdentifier = CheckForDefault;
compileEnvironment.ResolveSpecialIdentifier = ResolveForDefault;
compileEnvironment.CheckSpecialMember = CheckForMemberDefault;
compileEnvironment.ResolveSpecialFunction = AJumpProcessing;
compileEnvironment.CheckCustomGlobalFunctions = ResolveGlobalCustomFunction;
compileEnvironment.CustomBuiltinNew = "BuiltinNewDoom";
}

View file

@ -0,0 +1,112 @@
#pragma once
#include "codegen.h"
#include "actor.h"
//==========================================================================
//
// FxActionSpecialCall
//
//==========================================================================
class FxActionSpecialCall : public FxExpression
{
int Special;
FxExpression *Self;
FArgumentList ArgList;
public:
FxActionSpecialCall(FxExpression *self, int special, FArgumentList &args, const FScriptPosition &pos);
~FxActionSpecialCall();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxClassDefaults
//
//==========================================================================
class FxClassDefaults : public FxExpression
{
FxExpression *obj;
public:
FxClassDefaults(FxExpression *, const FScriptPosition &);
~FxClassDefaults();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// FxGetDefaultByType
//
//==========================================================================
class FxGetDefaultByType : public FxExpression
{
FxExpression *Self;
public:
FxGetDefaultByType(FxExpression *self);
~FxGetDefaultByType();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
// Only used to resolve the old jump by index feature of DECORATE
//
//==========================================================================
class FxStateByIndex : public FxExpression
{
unsigned index;
public:
FxStateByIndex(int i, const FScriptPosition &pos) : FxExpression(EFX_StateByIndex, pos)
{
index = i;
}
FxExpression *Resolve(FCompileContext&);
};
//==========================================================================
//
// Same as above except for expressions which means it will have to be
// evaluated at runtime
//
//==========================================================================
class FxRuntimeStateIndex : public FxExpression
{
FxExpression *Index;
int symlabel;
public:
FxRuntimeStateIndex(FxExpression *index);
~FxRuntimeStateIndex();
FxExpression *Resolve(FCompileContext&);
ExpEmit Emit(VMFunctionBuilder *build);
};
//==========================================================================
//
//
//
//==========================================================================
class FxMultiNameState : public FxExpression
{
PClassActor *scope;
TArray<FName> names;
public:
FxMultiNameState(const char *statestring, const FScriptPosition &pos, PClassActor *checkclass = nullptr);
FxExpression *Resolve(FCompileContext&);
};

View file

@ -38,7 +38,7 @@
#include "c_cvars.h"
#include "jit.h"
EXTERN_CVAR(Bool, strictdecorate);
CVAR(Bool, strictdecorate, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
struct VMRemap
{

View file

@ -43,6 +43,7 @@
#include "a_pickups.h"
#include "thingdef.h"
#include "backend/codegen.h"
#include "backend/codegen_doom.h"
FRandom pr_exrandom ("EX_Random");

View file

@ -44,6 +44,7 @@
#include "thingdef.h"
#include "a_morph.h"
#include "backend/codegen.h"
#include "backend/codegen_doom.h"
#include "filesystem.h"
#include "v_text.h"
#include "m_argv.h"
@ -53,7 +54,7 @@
#endif // !_MSC_VER
void ParseOldDecoration(FScanner &sc, EDefinitionType def, PNamespace *ns);
CVAR(Bool, strictdecorate, false, CVAR_GLOBALCONFIG | CVAR_ARCHIVE)
EXTERN_CVAR(Bool, strictdecorate);
//==========================================================================

View file

@ -44,6 +44,7 @@
#include "p_local.h"
#include "thingdef.h"
#include "backend/codegen.h"
#include "backend/codegen_doom.h"
#ifndef _MSC_VER
#include "i_system.h" // for strlwr()
#endif // !_MSC_VER

View file

@ -406,6 +406,7 @@ void CheckDropItems(const PClassActor *const obj)
void ParseScripts();
void ParseAllDecorate();
void SynthesizeFlagFields();
void SetDoomCompileEnvironment();
void LoadActors()
{
@ -414,6 +415,7 @@ void LoadActors()
timer.Reset(); timer.Clock();
FScriptPosition::ResetErrorCounter();
SetDoomCompileEnvironment();
InitThingdef();
FScriptPosition::StrictErrors = true;
ParseScripts();

View file

@ -40,6 +40,7 @@
#include "version.h"
#include "zcc_parser.h"
#include "zcc_compile.h"
#include "templates.h"
TArray<FString> Includes;
TArray<FScriptPosition> IncludeLocs;

View file

@ -426,6 +426,7 @@ class Object native
// These must be defined in some class, so that the compiler can find them. Object is just fine, as long as they are private to external code.
private native static Object BuiltinNew(Class<Object> cls, int outerclass, int compatibility);
private native static Object BuiltinNewDoom(Class<Object> cls, int outerclass, int compatibility);
private native static int BuiltinRandom(voidptr rng, int min, int max);
private native static double BuiltinFRandom(voidptr rng, double min, double max);
private native static int BuiltinRandom2(voidptr rng, int mask);