/* ** thingdef_exp.cpp ** ** Expression parsing for DECORATE ** **--------------------------------------------------------------------------- ** Copyright 2005 Jan Cholasta ** 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. ** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be ** covered by the terms of the GNU General Public License as published by ** the Free Software Foundation; either version 2 of the License, or (at ** your option) any later version. ** ** 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 "actor.h" #include "sc_man.h" #include "tarray.h" #include "templates.h" #include "cmdlib.h" #include "i_system.h" #include "m_random.h" #include "a_pickups.h" #include "thingdef.h" #include "p_lnspec.h" #include "doomstat.h" #include "thingdef_exp.h" FRandom pr_exrandom ("EX_Random"); static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls); static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls); static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls); static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls); static FxExpression *ParseAtan2(FScanner &sc, FName identifier, PClassActor *cls); static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls); static FxExpression *ParseClamp(FScanner &sc, PClassActor *cls); // // ParseExpression // [GRB] Parses an expression and stores it into Expression array // It's worth mentioning that this is using C++ operator precedence // static FxExpression *ParseExpressionM (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionL (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionK (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionJ (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionI (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionH (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionG (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionF (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionE (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionD (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionC (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionB (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpressionA (FScanner &sc, PClassActor *cls); static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls); FxExpression *ParseExpression (FScanner &sc, PClassActor *cls, bool mustresolve) { FxExpression *data = ParseExpressionM (sc, cls); if (mustresolve) { FCompileContext ctx(cls); data = data->Resolve(ctx); } return data; } static FxExpression *ParseExpressionM (FScanner &sc, PClassActor *cls) { FxExpression *base = ParseExpressionL (sc, cls); if (sc.CheckToken('?')) { FxExpression *truex = ParseExpressionM (sc, cls); sc.MustGetToken(':'); FxExpression *falsex = ParseExpressionM (sc, cls); return new FxConditional(base, truex, falsex); } else if (sc.CheckToken('=')) { FxExpression *right = ParseExpressionM(sc, cls); return new FxAssign(base, right); } else { FxBinary *exp; FxAssignSelf *left = new FxAssignSelf(sc); sc.GetToken(); switch (sc.TokenType) { case TK_AddEq: exp = new FxAddSub('+', left, nullptr); break; case TK_SubEq: exp = new FxAddSub('-', left, nullptr); break; case TK_MulEq: exp = new FxMulDiv('*', left, nullptr); break; case TK_DivEq: exp = new FxMulDiv('/', left, nullptr); break; case TK_ModEq: exp = new FxMulDiv('%', left, nullptr); break; case TK_LShiftEq: exp = new FxBinaryInt(TK_LShift, left, nullptr); break; case TK_RShiftEq: exp = new FxBinaryInt(TK_RShift, left, nullptr); break; case TK_URShiftEq: exp = new FxBinaryInt(TK_URShift, left, nullptr); break; case TK_AndEq: exp = new FxBinaryInt('&', left, nullptr); break; case TK_XorEq: exp = new FxBinaryInt('^', left, nullptr); break; case TK_OrEq: exp = new FxBinaryInt('|', left, nullptr); break; default: sc.UnGet(); delete left; return base; } exp->right = ParseExpressionM(sc, cls); FxAssign *ret = new FxAssign(base, exp); left->Assignment = ret; return ret; } } static FxExpression *ParseExpressionL (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionK (sc, cls); while (sc.CheckToken(TK_OrOr)) { FxExpression *right = ParseExpressionK (sc, cls); tmp = new FxBinaryLogical(TK_OrOr, tmp, right); } return tmp; } static FxExpression *ParseExpressionK (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionJ (sc, cls); while (sc.CheckToken(TK_AndAnd)) { FxExpression *right = ParseExpressionJ (sc, cls); tmp = new FxBinaryLogical(TK_AndAnd, tmp, right); } return tmp; } static FxExpression *ParseExpressionJ (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionI (sc, cls); while (sc.CheckToken('|')) { FxExpression *right = ParseExpressionI (sc, cls); tmp = new FxBinaryInt('|', tmp, right); } return tmp; } static FxExpression *ParseExpressionI (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionH (sc, cls); while (sc.CheckToken('^')) { FxExpression *right = ParseExpressionH (sc, cls); tmp = new FxBinaryInt('^', tmp, right); } return tmp; } static FxExpression *ParseExpressionH (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionG (sc, cls); while (sc.CheckToken('&')) { FxExpression *right = ParseExpressionG (sc, cls); tmp = new FxBinaryInt('&', tmp, right); } return tmp; } static FxExpression *ParseExpressionG (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionF (sc, cls); while (sc.GetToken() && (sc.TokenType == TK_Eq || sc.TokenType == TK_Neq)) { int token = sc.TokenType; FxExpression *right = ParseExpressionF (sc, cls); tmp = new FxCompareEq(token, tmp, right); } if (!sc.End) sc.UnGet(); return tmp; } static FxExpression *ParseExpressionF (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionE (sc, cls); while (sc.GetToken() && (sc.TokenType == '<' || sc.TokenType == '>' || sc.TokenType == TK_Leq || sc.TokenType == TK_Geq)) { int token = sc.TokenType; FxExpression *right = ParseExpressionE (sc, cls); tmp = new FxCompareRel(token, tmp, right); } if (!sc.End) sc.UnGet(); return tmp; } static FxExpression *ParseExpressionE (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionD (sc, cls); while (sc.GetToken() && (sc.TokenType == TK_LShift || sc.TokenType == TK_RShift || sc.TokenType == TK_URShift)) { int token = sc.TokenType; FxExpression *right = ParseExpressionD (sc, cls); tmp = new FxBinaryInt(token, tmp, right); } if (!sc.End) sc.UnGet(); return tmp; } static FxExpression *ParseExpressionD (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionC (sc, cls); while (sc.GetToken() && (sc.TokenType == '+' || sc.TokenType == '-')) { int token = sc.TokenType; FxExpression *right = ParseExpressionC (sc, cls); tmp = new FxAddSub(token, tmp, right); } if (!sc.End) sc.UnGet(); return tmp; } static FxExpression *ParseExpressionC (FScanner &sc, PClassActor *cls) { FxExpression *tmp = ParseExpressionB (sc, cls); while (sc.GetToken() && (sc.TokenType == '*' || sc.TokenType == '/' || sc.TokenType == '%')) { int token = sc.TokenType; FxExpression *right = ParseExpressionB (sc, cls); tmp = new FxMulDiv(token, tmp, right); } if (!sc.End) sc.UnGet(); return tmp; } static FxExpression *ParseExpressionB (FScanner &sc, PClassActor *cls) { sc.GetToken(); switch(sc.TokenType) { case '~': return new FxUnaryNotBitwise(ParseExpressionA (sc, cls)); case '!': return new FxUnaryNotBoolean(ParseExpressionA (sc, cls)); case '-': return new FxMinusSign(ParseExpressionA (sc, cls)); case '+': return new FxPlusSign(ParseExpressionA (sc, cls)); case TK_Incr: case TK_Decr: return new FxPreIncrDecr(ParseExpressionA(sc, cls), sc.TokenType); default: sc.UnGet(); return ParseExpressionA (sc, cls); } } //========================================================================== // // ParseExpressionB // //========================================================================== static FxExpression *ParseExpressionA (FScanner &sc, PClassActor *cls) { FxExpression *base_expr = ParseExpression0 (sc, cls); while(1) { FScriptPosition pos(sc); #if 0 if (sc.CheckToken('.')) { if (sc.CheckToken(TK_Default)) { sc.MustGetToken('.'); base_expr = new FxClassDefaults(base_expr, pos); } sc.MustGetToken(TK_Identifier); FName FieldName = sc.String; pos = sc; /* later! if (SC_CheckToken('(')) { if (base_expr->IsDefaultObject()) { SC_ScriptError("Cannot call methods for default."); } base_expr = ParseFunctionCall(base_expr, FieldName, false, false, pos); } else */ { base_expr = new FxDotIdentifier(base_expr, FieldName, pos); } } else #endif if (sc.CheckToken('[')) { FxExpression *index = ParseExpressionM(sc, cls); sc.MustGetToken(']'); base_expr = new FxArrayElement(base_expr, index); } else if (sc.CheckToken(TK_Incr)) { return new FxPostIncrDecr(base_expr, TK_Incr); } else if (sc.CheckToken(TK_Decr)) { return new FxPostIncrDecr(base_expr, TK_Decr); } else break; } return base_expr; } static FxExpression *ParseExpression0 (FScanner &sc, PClassActor *cls) { FScriptPosition scpos(sc); if (sc.CheckToken('(')) { FxExpression *data = ParseExpressionM (sc, cls); sc.MustGetToken(')'); return data; } else if (sc.CheckToken(TK_True)) { return new FxConstant(true, scpos); } else if (sc.CheckToken(TK_False)) { return new FxConstant(false, scpos); } else if (sc.CheckToken(TK_IntConst)) { return new FxConstant(sc.Number, scpos); } else if (sc.CheckToken(TK_FloatConst)) { return new FxConstant(sc.Float, scpos); } else if (sc.CheckToken(TK_NameConst)) { return new FxConstant(sc.Name, scpos); } else if (sc.CheckToken(TK_StringConst)) { // String parameters are converted to names. Technically, this should be // done at a higher level, as needed, but since no functions take string // arguments and ACS_NamedExecuteWithResult/CallACS need names, this is // a cheap way to get them working when people use "name" instead of 'name'. return new FxConstant(FName(sc.String), scpos); } else if (sc.CheckToken(TK_Bool)) { sc.MustGetToken('('); FxExpression *exp = ParseExpressionM(sc, cls); sc.MustGetToken(')'); return new FxBoolCast(exp); } else if (sc.CheckToken(TK_Int)) { sc.MustGetToken('('); FxExpression *exp = ParseExpressionM(sc, cls); sc.MustGetToken(')'); return new FxIntCast(exp); } else if (sc.CheckToken(TK_Float)) { sc.MustGetToken('('); FxExpression *exp = ParseExpressionM(sc, cls); sc.MustGetToken(')'); return new FxFloatCast(exp); } else if (sc.CheckToken(TK_State)) { sc.MustGetToken('('); FxExpression *exp; if (sc.CheckToken(TK_StringConst)) { if (sc.String[0] == 0 || sc.Compare("None")) { exp = new FxConstant((FState*)nullptr, sc); } else { exp = new FxMultiNameState(sc.String, sc); } } else { exp = new FxRuntimeStateIndex(ParseExpressionM(sc, cls)); } sc.MustGetToken(')'); return exp; } else if (sc.CheckToken(TK_Identifier)) { FName identifier = FName(sc.String); FArgumentList *args; PFunction *func; switch (identifier) { case NAME_Random: case NAME_FRandom: return ParseRandom(sc, identifier, cls); case NAME_RandomPick: case NAME_FRandomPick: return ParseRandomPick(sc, identifier, cls); case NAME_Random2: return ParseRandom2(sc, cls); default: if (cls != nullptr) { func = dyn_cast(cls->Symbols.FindSymbol(identifier, true)); // There is an action function ACS_NamedExecuteWithResult which must be ignored here for this to work. if (func != nullptr && identifier != NAME_ACS_NamedExecuteWithResult) { args = new FArgumentList; if (sc.CheckToken('(')) { sc.UnGet(); ParseFunctionParameters(sc, cls, *args, func, "", nullptr); } if (args->Size() == 0) { delete args; args = nullptr; } return new FxVMFunctionCall(func, args, sc); } } break; } if (sc.CheckToken('(')) { switch (identifier) { case NAME_Min: case NAME_Max: return ParseMinMax(sc, identifier, cls); case NAME_Clamp: return ParseClamp(sc, cls); case NAME_Abs: return ParseAbs(sc, cls); case NAME_ATan2: case NAME_VectorAngle: return ParseAtan2(sc, identifier, cls); default: args = new FArgumentList; try { if (!sc.CheckToken(')')) { do { args->Push(ParseExpressionM (sc, cls)); } while (sc.CheckToken(',')); sc.MustGetToken(')'); } return new FxFunctionCall(NULL, identifier, args, sc); } catch (...) { delete args; throw; } break; } } else { return new FxIdentifier(identifier, sc); } } else { FString tokname = sc.TokenName(sc.TokenType, sc.String); sc.ScriptError ("Unexpected token %s", tokname.GetChars()); } return NULL; } static FRandom *ParseRNG(FScanner &sc) { FRandom *rng; if (sc.CheckToken('[')) { sc.MustGetToken(TK_Identifier); rng = FRandom::StaticFindRNG(sc.String); sc.MustGetToken(']'); } else { rng = &pr_exrandom; } return rng; } static FxExpression *ParseRandom(FScanner &sc, FName identifier, PClassActor *cls) { FRandom *rng = ParseRNG(sc); sc.MustGetToken('('); FxExpression *min = ParseExpressionM (sc, cls); sc.MustGetToken(','); FxExpression *max = ParseExpressionM (sc, cls); sc.MustGetToken(')'); if (identifier == NAME_Random) { return new FxRandom(rng, min, max, sc); } else { return new FxFRandom(rng, min, max, sc); } } static FxExpression *ParseRandomPick(FScanner &sc, FName identifier, PClassActor *cls) { bool floaty = identifier == NAME_FRandomPick; FRandom *rng; TArray list; list.Clear(); int index = 0; rng = ParseRNG(sc); sc.MustGetToken('('); for (;;) { FxExpression *expr = ParseExpressionM(sc, cls); list.Push(expr); if (sc.CheckToken(')')) break; sc.MustGetToken(','); } return new FxRandomPick(rng, list, floaty, sc); } static FxExpression *ParseRandom2(FScanner &sc, PClassActor *cls) { FRandom *rng = ParseRNG(sc); FxExpression *mask = NULL; sc.MustGetToken('('); if (!sc.CheckToken(')')) { mask = ParseExpressionM(sc, cls); sc.MustGetToken(')'); } return new FxRandom2(rng, mask, sc); } static FxExpression *ParseAbs(FScanner &sc, PClassActor *cls) { FxExpression *x = ParseExpressionM (sc, cls); sc.MustGetToken(')'); return new FxAbs(x); } static FxExpression *ParseAtan2(FScanner &sc, FName identifier, PClassActor *cls) { FxExpression *a = ParseExpressionM(sc, cls); sc.MustGetToken(','); FxExpression *b = ParseExpressionM(sc, cls); sc.MustGetToken(')'); return identifier == NAME_ATan2 ? new FxATan2(a, b, sc) : new FxATan2(b, a, sc); } static FxExpression *ParseMinMax(FScanner &sc, FName identifier, PClassActor *cls) { TArray list; for (;;) { FxExpression *expr = ParseExpressionM(sc, cls); list.Push(expr); if (sc.CheckToken(')')) break; sc.MustGetToken(','); } return new FxMinMax(list, identifier, sc); } static FxExpression *ParseClamp(FScanner &sc, PClassActor *cls) { FxExpression *src = ParseExpressionM(sc, cls); sc.MustGetToken(','); FxExpression *min = ParseExpressionM(sc, cls); sc.MustGetToken(','); FxExpression *max = ParseExpressionM(sc, cls); sc.MustGetToken(')'); // Build clamp(a,x,y) as min(max(a,x),y) TArray list(2); list.Reserve(2); list[0] = src; list[1] = min; FxExpression *maxexpr = new FxMinMax(list, NAME_Max, sc); list[0] = maxexpr; list[1] = max; return new FxMinMax(list, NAME_Min, sc); }