From 6a8ab9a4d32a61f535b87922b6f2460f119003cb Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 12 Oct 2016 17:50:23 +0200 Subject: [PATCH] - 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. --- src/d_dehacked.cpp | 2 +- src/thingdef/thingdef_exp.cpp | 2 +- src/thingdef/thingdef_exp.h | 3 +- src/thingdef/thingdef_expression.cpp | 56 ++++-- src/thingdef/thingdef_states.cpp | 4 +- src/zscript/zcc-parse.lemon | 29 ++- src/zscript/zcc_compile.cpp | 279 ++++++++++++++++++++++++++- src/zscript/zcc_compile.h | 4 + src/zscript/zcc_parser.cpp | 17 -- src/zscript/zcc_parser.h | 8 + 10 files changed, 362 insertions(+), 42 deletions(-) diff --git a/src/d_dehacked.cpp b/src/d_dehacked.cpp index dd71fdf75..d062ce533 100644 --- a/src/d_dehacked.cpp +++ b/src/d_dehacked.cpp @@ -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); diff --git a/src/thingdef/thingdef_exp.cpp b/src/thingdef/thingdef_exp.cpp index cad6198f0..11baef722 100644 --- a/src/thingdef/thingdef_exp.cpp +++ b/src/thingdef/thingdef_exp.cpp @@ -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); } } diff --git a/src/thingdef/thingdef_exp.h b/src/thingdef/thingdef_exp.h index 5fe8f252f..65c3ace63 100644 --- a/src/thingdef/thingdef_exp.h +++ b/src/thingdef/thingdef_exp.h @@ -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(); diff --git a/src/thingdef/thingdef_expression.cpp b/src/thingdef/thingdef_expression.cpp index c2b2d25e6..ae2639b73 100644 --- a/src/thingdef/thingdef_expression.cpp +++ b/src/thingdef/thingdef_expression.cpp @@ -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,28 +3969,55 @@ ExpEmit FxVMFunctionCall::Emit(VMFunctionBuilder *build) return reg; } } - // Emit code to pass implied parameters - if (Function->Flags & VARF_Method) + + // 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)) { - build->Emit(OP_PARAM, 0, REGT_POINTER, 0); - count += 1; + // Emit code to pass implied parameters + if (Function->Flags & VARF_Method) + { + build->Emit(OP_PARAM, 0, REGT_POINTER, 0); + count += 1; + } + if (Function->Flags & VARF_Action) + { + static_assert(NAP == 3, "This code needs to be updated if NAP changes"); + if (build->IsActionFunc) + { + build->Emit(OP_PARAM, 0, REGT_POINTER, 1); + build->Emit(OP_PARAM, 0, REGT_POINTER, 2); + } + else + { + int null = build->GetConstantAddress(nullptr, ATAG_GENERIC); + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, null); + build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, null); + } + count += 2; + } } - if (Function->Flags & VARF_Action) + else { - static_assert(NAP == 3, "This code needs to be updated if NAP changes"); - if (build->IsActionFunc) + // 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); - build->Emit(OP_PARAM, 0, REGT_POINTER, 2); + count += 1; } - else + // 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)) { - int null = build->GetConstantAddress(nullptr, ATAG_GENERIC); - build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, null); - build->Emit(OP_PARAM, 0, REGT_POINTER | REGT_KONST, null); + // This case should be eliminated in the analyzing stage. + I_Error("Cannot call action function from non-action functions."); } - count += 2; } + // Emit code to pass explicit parameters if (ArgList != NULL) { diff --git a/src/thingdef/thingdef_states.cpp b/src/thingdef/thingdef_states.cpp index 707de3045..3b692391c 100644 --- a/src/thingdef/thingdef_states.cpp +++ b/src/thingdef/thingdef_states.cpp @@ -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; diff --git a/src/zscript/zcc-parse.lemon b/src/zscript/zcc-parse.lemon index ae03248cc..7213b4dbc 100644 --- a/src/zscript/zcc-parse.lemon +++ b/src/zscript/zcc-parse.lemon @@ -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*/ } diff --git a/src/zscript/zcc_compile.cpp b/src/zscript/zcc_compile.cpp index 7e718b42f..19af0e7c1 100644 --- a/src/zscript/zcc_compile.cpp +++ b/src/zscript/zcc_compile.cpp @@ -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(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(duration); + if (func->Function == nullptr) return false; + if (func->Function->NodeType != AST_ExprID) return false; + auto f2 = static_cast(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(af); + assert(fc->Function->NodeType == AST_ExprID); + auto id = static_cast(fc->Function); + + PFunction *afd = dyn_cast(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(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(st); + statename = FName(sl->Label); + statedef.AddStateLabel(statename); + break; + } + case AST_StateLine: + { + auto sl = static_cast(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(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(func->Parameters->SiblingNext)->Value); + if (v1 > v2) std::swap(v1, v2); + state.Tics = (int16_t)clamp(v1, 0, INT16_MAX); + state.TicRange = (uint16_t)clamp(v2 - v1, 0, UINT16_MAX); + } + else + { + auto duration = Simplify(sl->Duration, &c->Type()->Symbols); + if (duration->Operation == PEX_ConstValue) + { + state.Tics = (int16_t)clamp(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(Simplify(sl->Offset, &c->Type()->Symbols)); + auto o2 = static_cast(Simplify(static_cast(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(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(c->Type()), sl->Action); + if (code != nullptr) + { + auto tcall = new FStateTempCall; + tcall->Code = code; + tcall->ActorClass = static_cast(c->Type()); + tcall->FirstState = statedef.GetStateCount() - count; + tcall->NumStates = count; + StateTempCalls.Push(tcall); + } + } + break; + } + case AST_StateGoto: + { + auto sg = static_cast(st); + statename = ""; + if (sg->Qualifier != nullptr) + { + statename << sg->Qualifier->Id << "::"; + } + auto part = sg->Label; + do + { + statename << part->Id << '.'; + part = static_cast(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(st->SiblingNext); + } while (st != s->Body); + } + } +} diff --git a/src/zscript/zcc_compile.h b/src/zscript/zcc_compile.h index e5fb241f3..74d83f529 100644 --- a/src/zscript/zcc_compile.h +++ b/src/zscript/zcc_compile.h @@ -4,6 +4,7 @@ struct Baggage; struct FPropertyInfo; class AActor; +class FxExpression; struct ZCC_StructWork { @@ -46,6 +47,7 @@ struct ZCC_ClassWork TArray Fields; TArray Defaults; TArray Functions; + TArray 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 Constants; TArray Structs; diff --git a/src/zscript/zcc_parser.cpp b/src/zscript/zcc_parser.cpp index ce2dc98e7..0d69141f0 100644 --- a/src/zscript/zcc_parser.cpp +++ b/src/zscript/zcc_parser.cpp @@ -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() diff --git a/src/zscript/zcc_parser.h b/src/zscript/zcc_parser.h index 0cb3f79ae..2a73c8681 100644 --- a/src/zscript/zcc_parser.h +++ b/src/zscript/zcc_parser.h @@ -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; };