- implemented the state compiler. So far all it can handle is parameter-less functions.

To do the rest, some cleanup is needed first, to untangle the DECORATE parser from the actual code generation so that the low end stuff can actually be reused here instead of having to be redone.
This commit is contained in:
Christoph Oelckers 2016-10-12 17:50:23 +02:00
parent 900644e465
commit 6a8ab9a4d3
10 changed files with 362 additions and 42 deletions

View file

@ -804,7 +804,7 @@ void SetDehParams(FState *state, int codepointer)
}
else
{
VMFunctionBuilder buildit;
VMFunctionBuilder buildit(true);
// Allocate registers used to pass parameters in.
// self, stateowner, state (all are pointers)
buildit.Registers[REGT_POINTER].Get(NAP);

View file

@ -515,7 +515,7 @@ static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls)
args = nullptr;
}
return new FxVMFunctionCall(func, args, sc);
return new FxVMFunctionCall(func, args, sc, false);
}
}

View file

@ -948,11 +948,12 @@ public:
class FxVMFunctionCall : public FxExpression
{
bool EmitTail;
bool OwnerIsSelf; // ZSCRIPT makes the state's owner the self pointer to ensure proper type handling
PFunction *Function;
FArgumentList *ArgList;
public:
FxVMFunctionCall(PFunction *func, FArgumentList *args, const FScriptPosition &pos);
FxVMFunctionCall(PFunction *func, FArgumentList *args, const FScriptPosition &pos, bool ownerisself);
~FxVMFunctionCall();
FxExpression *Resolve(FCompileContext&);
PPrototype *ReturnProto();

View file

@ -3858,12 +3858,13 @@ ExpEmit FxActionSpecialCall::Emit(VMFunctionBuilder *build)
//
//==========================================================================
FxVMFunctionCall::FxVMFunctionCall(PFunction *func, FArgumentList *args, const FScriptPosition &pos)
FxVMFunctionCall::FxVMFunctionCall(PFunction *func, FArgumentList *args, const FScriptPosition &pos, bool ownerisself)
: FxExpression(pos)
{
Function = func;
ArgList = args;
EmitTail = false;
OwnerIsSelf = ownerisself;
}
//==========================================================================
@ -3968,6 +3969,13 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
return reg;
}
}
// Passing the caller as 'self' is a serious design mistake in DECORATE because it mixes up the types of the two actors involved.
// For ZSCRIPT 'self' is properly used for the state's owning actor, meaning we have to pass the second argument here.
// If both functions are non-action or both are action, there is no need for special treatment.
if (!OwnerIsSelf || (!!(Function->Flags & VARF_Action) == build->IsActionFunc))
{
// Emit code to pass implied parameters
if (Function->Flags & VARF_Method)
{
@ -3990,6 +3998,26 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build)
}
count += 2;
}
}
else
{
// There are two possibilities here:
// Calling a non-action function from an action function.
// In that case the 'stateowner' pointer needs to be used as self.
if (build->IsActionFunc && (Function->Flags & VARF_Method))
{
build->Emit(OP_PARAM, 0, REGT_POINTER, 1);
count += 1;
}
// and calling an action function from a non-action function.
// This must be blocked because it lacks crucial information.
if (!build->IsActionFunc && (Function->Flags & VARF_Action))
{
// This case should be eliminated in the analyzing stage.
I_Error("Cannot call action function from non-action functions.");
}
}
// Emit code to pass explicit parameters
if (ArgList != NULL)
{

View file

@ -100,7 +100,7 @@ FxVMFunctionCall *DoActionSpecials(FScanner &sc, FState & state, Baggage &bag)
{
sc.ScriptError ("Too many arguments to %s", specname.GetChars());
}
return new FxVMFunctionCall(FindGlobalActionFunction("A_CallSpecial"), args, sc);
return new FxVMFunctionCall(FindGlobalActionFunction("A_CallSpecial"), args, sc, false);
}
return NULL;
}
@ -582,7 +582,7 @@ FxVMFunctionCall *ParseAction(FScanner &sc, FState state, FString statestring, B
{
FArgumentList *args = new FArgumentList;
ParseFunctionParameters(sc, bag.Info, *args, afd, statestring, &bag.statedef);
call = new FxVMFunctionCall(afd, args->Size() > 0 ? args : NULL, sc);
call = new FxVMFunctionCall(afd, args->Size() > 0 ? args : NULL, sc, false);
if (args->Size() == 0)
{
delete args;

View file

@ -40,6 +40,7 @@ static void SetNodeLine(ZCC_TreeNode *name, int line)
struct StateOpts {
ZCC_Expression *Offset;
ZCC_ExprConstant *Lights;
bool Bright;
bool Fast;
bool Slow;
@ -47,7 +48,8 @@ static void SetNodeLine(ZCC_TreeNode *name, int line)
bool CanRaise;
void Zero() {
Offset = NULL;
Offset = nullptr;
Lights = nullptr;
Bright = false;
Fast = false;
Slow = false;
@ -461,6 +463,7 @@ state_line(X) ::= NWS(A) NWS(B) expr(E) state_opts(C) state_action(D).
line->bNoDelay = C.NoDelay;
line->bCanRaise = C.CanRaise;
line->Offset = C.Offset;
line->Lights = C.Lights;
line->Action = D;
X = line;
}
@ -472,10 +475,28 @@ state_opts(X) ::= state_opts(A) SLOW. { A.Slow = true; X = A; /*X-overwri
state_opts(X) ::= state_opts(A) NODELAY. { A.NoDelay = true; X = A; /*X-overwrites-A*/ }
state_opts(X) ::= state_opts(A) CANRAISE. { A.CanRaise = true; X = A; /*X-overwrites-A*/ }
state_opts(X) ::= state_opts(A) OFFSET LPAREN expr(B) COMMA expr(C) RPAREN. { A.Offset = B; B->AppendSibling(C); X = A; /*X-overwrites-A*/ }
state_opts(X) ::= state_opts(A) LIGHT LPAREN light_list RPAREN. { X = A; /*X-overwrites-A*/ } ///FIXME: GZDoom would want to know this
state_opts(X) ::= state_opts(A) LIGHT LPAREN light_list(B) RPAREN. { X = A; /*X-overwrites-A*/ X.Lights = B; }
light_list ::= STRCONST.
light_list ::= light_list COMMA STRCONST.
%type light_list {ZCC_ExprConstant *}
light_list(X) ::= STRCONST(A).
{
NEW_AST_NODE(ExprConstant, strconst, A);
strconst->Operation = PEX_ConstValue;
strconst->Type = TypeString;
strconst->StringVal = A.String;
X = strconst;
}
light_list(X) ::= light_list(A) COMMA STRCONST(B).
{
NEW_AST_NODE(ExprConstant, strconst, B);
strconst->Operation = PEX_ConstValue;
strconst->Type = TypeString;
strconst->StringVal = B.String;
A->AppendSibling(strconst);
X = A; /*X-overwrites-A*/
}
/* A state action can be either a compound statement or a single action function call. */
state_action(X) ::= LBRACE statement_list(A) scanner_mode RBRACE. { X = A; /*X-overwrites-A*/ }

View file

@ -46,6 +46,7 @@
#include "v_text.h"
#include "p_lnspec.h"
#include "gdtoa.h"
#include "thingdef_exp.h"
#define DEFINING_CONST ((PSymbolConst *)(void *)1)
@ -109,8 +110,8 @@ void ZCCCompiler::ProcessClass(ZCC_Class *cnode, PSymbolTreeNode *treenode)
enumType = nullptr;
break;
// todo
case AST_States:
cls->States.Push(static_cast<ZCC_States *>(node));
break;
case AST_FuncDeclarator:
@ -368,6 +369,7 @@ int ZCCCompiler::Compile()
CompileAllFields();
InitDefaults();
InitFunctions();
CompileStates();
return ErrorCount;
}
@ -1856,7 +1858,6 @@ void ZCCCompiler::InitFunctions()
{
for (auto f : c->Functions)
{
Printf("processing function %s\n", FName(f->Name).GetChars());
rets.Clear();
args.Clear();
argflags.Clear();
@ -1966,3 +1967,277 @@ void ZCCCompiler::InitFunctions()
}
}
//==========================================================================
//
// very complicated check for random duration.
//
//==========================================================================
static bool CheckRandom(ZCC_Expression *duration)
{
if (duration->NodeType != AST_ExprFuncCall) return false;
auto func = static_cast<ZCC_ExprFuncCall *>(duration);
if (func->Function == nullptr) return false;
if (func->Function->NodeType != AST_ExprID) return false;
auto f2 = static_cast<ZCC_ExprID *>(func->Function);
return f2->Identifier == NAME_Random;
}
//==========================================================================
//
// Sets up the action function call
//
//==========================================================================
FxExpression *ZCCCompiler::SetupActionFunction(PClassActor *cls, ZCC_TreeNode *af)
{
// We have 3 cases to consider here:
// 1. An action function without parameters. This can be called directly
// 2. An action functon with parameters or a non-action function. This needs to be wrapped into a helper function to set everything up.
// 3. An anonymous function.
// 1. and 2. are exposed through AST_ExprFunctionCall
if (af->NodeType == AST_ExprFuncCall)
{
auto fc = static_cast<ZCC_ExprFuncCall *>(af);
assert(fc->Function->NodeType == AST_ExprID);
auto id = static_cast<ZCC_ExprID *>(fc->Function);
PFunction *afd = dyn_cast<PFunction>(cls->Symbols.FindSymbol(id->Identifier, true));
if (afd != nullptr)
{
if (fc->Parameters == nullptr && (afd->Flags & VARF_Action))
{
// This is the simple case which doesn't require work on the tree.
return new FxVMFunctionCall(afd, nullptr, *af, true);
}
else
{
// need to generate a function from the information.
}
}
else
{
Error(af, "%s: action function not found in %s", FName(id->Identifier).GetChars(), cls->TypeName.GetChars());
return nullptr;
}
}
Error(af, "Complex action functions not supported yet.");
return nullptr;
/*
bool hasfinalret;
tcall->Code = ParseActions(sc, state, statestring, bag, hasfinalret);
if (!hasfinalret && tcall->Code != nullptr)
{
static_cast<FxSequence *>(tcall->Code)->Add(new FxReturnStatement(nullptr, sc));
}
*/
}
//==========================================================================
//
// Compile the states
//
//==========================================================================
void ZCCCompiler::CompileStates()
{
for (auto c : Classes)
{
if (!c->Type()->IsDescendantOf(RUNTIME_CLASS(AActor)))
{
Error(c->cls, "%s: States can only be defined for actors.", c->Type()->TypeName.GetChars());
continue;
}
FString statename; // The state builder wants the label as one complete string, not separated into tokens.
FStateDefinitions statedef;
for (auto s : c->States)
{
auto st = s->Body;
do
{
switch (st->NodeType)
{
case AST_StateLabel:
{
auto sl = static_cast<ZCC_StateLabel *>(st);
statename = FName(sl->Label);
statedef.AddStateLabel(statename);
break;
}
case AST_StateLine:
{
auto sl = static_cast<ZCC_StateLine *>(st);
FState state;
memset(&state, 0, sizeof(state));
if (sl->Sprite->Len() != 4)
{
Error(sl, "Sprite name must be exactly 4 characters. Found '%s'", sl->Sprite->GetChars());
}
else
{
state.sprite = GetSpriteIndex(sl->Sprite->GetChars());
}
// It is important to call CheckRandom before Simplify, because Simplify will resolve the function's name to nonsense
// and there is little point fixing it because it is essentially useless outside of resolving constants.
if (CheckRandom(sl->Duration))
{
auto func = static_cast<ZCC_ExprFuncCall *>(Simplify(sl->Duration, &c->Type()->Symbols));
if (func->Parameters == func->Parameters->SiblingNext || func->Parameters != func->Parameters->SiblingNext->SiblingNext)
{
Error(sl, "Random duration requires exactly 2 parameters");
}
int v1 = GetInt(func->Parameters->Value);
int v2 = GetInt(static_cast<ZCC_FuncParm *>(func->Parameters->SiblingNext)->Value);
if (v1 > v2) std::swap(v1, v2);
state.Tics = (int16_t)clamp<int>(v1, 0, INT16_MAX);
state.TicRange = (uint16_t)clamp<int>(v2 - v1, 0, UINT16_MAX);
}
else
{
auto duration = Simplify(sl->Duration, &c->Type()->Symbols);
if (duration->Operation == PEX_ConstValue)
{
state.Tics = (int16_t)clamp<int>(GetInt(duration), -1, INT16_MAX);
state.TicRange = 0;
}
else
{
Error(sl, "Duration is not a constant");
}
}
state.Fullbright = sl->bBright;
state.Fast = sl->bFast;
state.Slow = sl->bSlow;
state.CanRaise = sl->bCanRaise;
if ((state.NoDelay = sl->bNoDelay))
{
if (statedef.GetStateLabelIndex(NAME_Spawn) != statedef.GetStateCount())
{
Warn(sl, "NODELAY only has an effect on the first state after 'Spawn:'");
}
}
if (sl->Offset != nullptr)
{
auto o1 = static_cast<ZCC_Expression *>(Simplify(sl->Offset, &c->Type()->Symbols));
auto o2 = static_cast<ZCC_Expression *>(Simplify(static_cast<ZCC_Expression *>(o1->SiblingNext), &c->Type()->Symbols));
if (o1->Operation != PEX_ConstValue || o2->Operation != PEX_ConstValue)
{
Error(o1, "State offsets must be constant");
}
else
{
state.Misc1 = GetInt(o1);
state.Misc2 = GetInt(o2);
}
}
#ifdef DYNLIGHT
if (sl->Lights != nullptr)
{
auto l = sl->Lights;
do
{
AddStateLight(&state, GetString(l));
l = static_cast<decltype(l)>(l->SiblingNext);
} while (l != sl->Lights);
}
#endif
int count = statedef.AddStates(&state, sl->Frames->GetChars());
if (count < 0)
{
Error(sl, "Invalid frame character string '%s'", sl->Frames->GetChars());
count = -count;
}
if (sl->Action != nullptr)
{
auto code = SetupActionFunction(static_cast<PClassActor *>(c->Type()), sl->Action);
if (code != nullptr)
{
auto tcall = new FStateTempCall;
tcall->Code = code;
tcall->ActorClass = static_cast<PClassActor *>(c->Type());
tcall->FirstState = statedef.GetStateCount() - count;
tcall->NumStates = count;
StateTempCalls.Push(tcall);
}
}
break;
}
case AST_StateGoto:
{
auto sg = static_cast<ZCC_StateGoto *>(st);
statename = "";
if (sg->Qualifier != nullptr)
{
statename << sg->Qualifier->Id << "::";
}
auto part = sg->Label;
do
{
statename << part->Id << '.';
part = static_cast<decltype(part)>(part->SiblingNext);
} while (part != sg->Label);
statename.Truncate((long)statename.Len() - 1); // remove the last '.' in the label name
if (sg->Offset != nullptr)
{
auto ofs = Simplify(sg->Offset, &c->Type()->Symbols);
if (ofs->Operation != PEX_ConstValue)
{
Error(sg, "Constant offset expected for GOTO");
}
else
{
int offset = GetInt(ofs);
if (offset < 0)
{
Error(sg, "GOTO offset must be positive");
offset = 0;
}
if (offset > 0)
{
statename.AppendFormat("+%d", offset);
}
}
}
if (!statedef.SetGotoLabel(statename))
{
Error(sg, "GOTO before first state");
}
break;
}
case AST_StateFail:
case AST_StateWait:
if (!statedef.SetWait())
{
Error(st, "%s before first state", st->NodeType == AST_StateFail ? "Fail" : "Wait");
continue;
}
break;
case AST_StateLoop:
if (!statedef.SetLoop())
{
Error(st, "LOOP before first state");
continue;
}
break;
case AST_StateStop:
if (!statedef.SetStop())
{
Error(st, "STOP before first state");
}
break;
default:
assert(0 && "Bad AST node in state");
}
st = static_cast<decltype(st)>(st->SiblingNext);
} while (st != s->Body);
}
}
}

View file

@ -4,6 +4,7 @@
struct Baggage;
struct FPropertyInfo;
class AActor;
class FxExpression;
struct ZCC_StructWork
{
@ -46,6 +47,7 @@ struct ZCC_ClassWork
TArray<ZCC_VarDeclarator *> Fields;
TArray<ZCC_Default *> Defaults;
TArray<ZCC_FuncDeclarator *> Functions;
TArray<ZCC_States *> States;
ZCC_ClassWork(ZCC_Class * s, PSymbolTreeNode *n)
{
@ -104,6 +106,8 @@ private:
const char *GetString(ZCC_Expression *expr, bool silent = false);
void InitFunctions();
void CompileStates();
FxExpression *SetupActionFunction(PClassActor *cls, ZCC_TreeNode *sl);
TArray<ZCC_ConstantDef *> Constants;
TArray<ZCC_StructWork *> Structs;

View file

@ -347,23 +347,6 @@ static void DoParse(int lumpnum)
symtable.SetName("Global_Node");
ZCCCompiler cc(state, NULL, symtable, GlobalSymbols);
cc.Compile();
// ... and another one afterward so we can see what the compiler does with the data.
#ifdef _DEBUG
if (f != NULL)
{
fclose(f);
}
FString ast = ZCC_PrintAST(state.TopNode);
FString filename = Wads.GetLumpFullName(lumpnum);
FString astfile = ExtractFileBase(filename, false);
astfile << "-after.ast";
f = fopen(astfile, "w");
if (f != NULL)
{
fputs(ast.GetChars(), f);
fclose(f);
}
#endif
}
void ParseScripts()

View file

@ -2,6 +2,7 @@
#define ZCC_PARSER_H
#include "memarena.h"
#include "sc_man.h"
struct ZCCToken
{
@ -176,6 +177,12 @@ struct ZCC_TreeNode
SiblingPrev = siblingend;
siblingend->SiblingNext = this;
}
operator FScriptPosition()
{
return FScriptPosition(*SourceName, SourceLoc);
}
};
struct ZCC_Identifier : ZCC_TreeNode
@ -276,6 +283,7 @@ struct ZCC_StateLine : ZCC_StatePart
FString *Frames;
ZCC_Expression *Duration;
ZCC_Expression *Offset;
ZCC_ExprConstant *Lights;
ZCC_TreeNode *Action;
};