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; };