diff --git a/src/common/engine/sc_man_scanner.re b/src/common/engine/sc_man_scanner.re index ccaf8fe0b35..1d18be71d4f 100644 --- a/src/common/engine/sc_man_scanner.re +++ b/src/common/engine/sc_man_scanner.re @@ -174,7 +174,7 @@ std2: /* Other keywords from UnrealScript */ 'abstract' { RET(TK_Abstract); } - 'foreach' { RET(TK_ForEach); } + 'foreach' { RET(ParseVersion >= MakeVersion(4, 10, 0)? TK_ForEach : TK_Identifier); } 'true' { RET(TK_True); } 'false' { RET(TK_False); } 'none' { RET(TK_None); } diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index b22a6da886f..a450ceb598a 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -7310,8 +7310,8 @@ FxClassMember::FxClassMember(FxExpression *x, PField* mem, const FScriptPosition // //========================================================================== -FxArrayElement::FxArrayElement(FxExpression *base, FxExpression *_index) -:FxExpression(EFX_ArrayElement, base->ScriptPosition) +FxArrayElement::FxArrayElement(FxExpression *base, FxExpression *_index, bool nob) +:FxExpression(EFX_ArrayElement, base->ScriptPosition), noboundscheck(nob) { Array=base; index = _index; @@ -7594,18 +7594,21 @@ ExpEmit FxArrayElement::Emit(VMFunctionBuilder *build) else { ExpEmit indexv(index->Emit(build)); - if (SizeAddr != ~0u || nestedarray) - { - build->Emit(OP_BOUND_R, indexv.RegNum, bound.RegNum); - bound.Free(build); - } - else if (arraytype->ElementCount > 65535) - { - build->Emit(OP_BOUND_K, indexv.RegNum, build->GetConstantInt(arraytype->ElementCount)); - } - else + if (!noboundscheck) // this is 'foreach' which is known to be inside the bounds. { - build->Emit(OP_BOUND, indexv.RegNum, arraytype->ElementCount); + if (SizeAddr != ~0u || nestedarray) + { + build->Emit(OP_BOUND_R, indexv.RegNum, bound.RegNum); + bound.Free(build); + } + else if (arraytype->ElementCount > 65535) + { + build->Emit(OP_BOUND_K, indexv.RegNum, build->GetConstantInt(arraytype->ElementCount)); + } + else + { + build->Emit(OP_BOUND, indexv.RegNum, arraytype->ElementCount); + } } if (!start.Konst) @@ -8338,7 +8341,7 @@ FxExpression *FxMemberFunctionCall::Resolve(FCompileContext& ctx) else if (Self->IsVector()) { // handle builtins: Vectors got 5. - if (MethodName == NAME_Length || MethodName == NAME_LengthSquared || MethodName == NAME_Unit || MethodName == NAME_Angle) + if (MethodName == NAME_Length || MethodName == NAME_LengthSquared || MethodName == NAME_Sum || MethodName == NAME_Unit || MethodName == NAME_Angle) { if (ArgList.Size() > 0) { @@ -10655,6 +10658,77 @@ ExpEmit FxForLoop::Emit(VMFunctionBuilder *build) return ExpEmit(); } +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +FxForEachLoop::FxForEachLoop(FName vn, FxExpression* arrayvar, FxExpression* arrayvar2, FxExpression* code, const FScriptPosition& pos) + : FxLoopStatement(EFX_ForEachLoop, pos), loopVarName(vn), Array(arrayvar), Array2(arrayvar2), Code(code) +{ + ValueType = TypeVoid; + if (Array != nullptr) Array->NeedResult = false; + if (Array2 != nullptr) Array2->NeedResult = false; + if (Code != nullptr) Code->NeedResult = false; +} + +FxForEachLoop::~FxForEachLoop() +{ + SAFE_DELETE(Array); + SAFE_DELETE(Array2); + SAFE_DELETE(Code); +} + +FxExpression* FxForEachLoop::DoResolve(FCompileContext& ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Array, ctx); + SAFE_RESOLVE(Array2, ctx); + + // Instead of writing a new code generator for this, convert this into + // + // int @size = array.Size(); + // for(int @i = 0; @i < @size; @i++) + // { + // let var = array[i]; + // body + // } + // and let the existing 'for' loop code sort out the rest. + + FName sizevar = "@size"; + FName itvar = "@i"; + FArgumentList al; + auto block = new FxCompoundStatement(ScriptPosition); + auto arraysize = new FxMemberFunctionCall(Array, NAME_Size, al, ScriptPosition); + auto size = new FxLocalVariableDeclaration(TypeSInt32, sizevar, arraysize, 0, ScriptPosition); + auto it = new FxLocalVariableDeclaration(TypeSInt32, itvar, new FxConstant(0, ScriptPosition), 0, ScriptPosition); + block->Add(size); + block->Add(it); + + auto cit = new FxLocalVariable(it, ScriptPosition); + auto csiz = new FxLocalVariable(size, ScriptPosition); + auto comp = new FxCompareRel('<', cit, csiz); // new FxIdentifier(itvar, ScriptPosition), new FxIdentifier(sizevar, ScriptPosition)); + + auto iit = new FxLocalVariable(it, ScriptPosition); + auto bump = new FxPreIncrDecr(iit, TK_Incr); + + auto ait = new FxLocalVariable(it, ScriptPosition); + auto access = new FxArrayElement(Array2, ait, true); // Note: Array must be a separate copy because these nodes cannot share the same element. + + auto assign = new FxLocalVariableDeclaration(TypeAuto, loopVarName, access, 0, ScriptPosition); + auto body = new FxCompoundStatement(ScriptPosition); + body->Add(assign); + body->Add(Code); + auto forloop = new FxForLoop(nullptr, comp, bump, body, ScriptPosition); + block->Add(forloop); + Array2 = Array = nullptr; + Code = nullptr; + delete this; + return block->Resolve(ctx); +} + + //========================================================================== // // FxJumpStatement @@ -11221,14 +11295,23 @@ FxExpression *FxLocalVariableDeclaration::Resolve(FCompileContext &ctx) delete this; return nullptr; } - SAFE_RESOLVE_OPT(Init, ctx); - if (Init->ValueType->RegType == REGT_NIL) + SAFE_RESOLVE(Init, ctx); + ValueType = Init->ValueType; + if (ValueType->RegType == REGT_NIL) { - ScriptPosition.Message(MSG_ERROR, "Cannot initialize non-scalar variable %s here", Name.GetChars()); - delete this; - return nullptr; + if (Init->IsStruct()) + { + ValueType = NewPointer(ValueType); + Init = new FxTypeCast(Init, ValueType, false); + SAFE_RESOLVE(Init, ctx); + } + else + { + ScriptPosition.Message(MSG_ERROR, "Cannot initialize non-scalar variable %s here", Name.GetChars()); + delete this; + return nullptr; + } } - ValueType = Init->ValueType; // check for undersized ints and floats. These are not allowed as local variables. if (IsInteger() && ValueType->Align < sizeof(int)) ValueType = TypeSInt32; else if (IsFloat() && ValueType->Align < sizeof(double)) ValueType = TypeFloat64; diff --git a/src/common/scripting/backend/codegen.h b/src/common/scripting/backend/codegen.h index a86d27cce18..364333d85c5 100644 --- a/src/common/scripting/backend/codegen.h +++ b/src/common/scripting/backend/codegen.h @@ -272,6 +272,7 @@ enum EFxType EFX_WhileLoop, EFX_DoWhileLoop, EFX_ForLoop, + EFX_ForEachLoop, EFX_JumpStatement, EFX_ReturnStatement, EFX_ClassTypeCast, @@ -349,6 +350,7 @@ class FxExpression bool IsArray() const { return ValueType->isArray() || (ValueType->isPointer() && ValueType->toPointer()->PointedType->isArray()); } bool isStaticArray() const { return (ValueType->isPointer() && ValueType->toPointer()->PointedType->isStaticArray()); } // can only exist in pointer form. bool IsDynamicArray() const { return (ValueType->isDynArray()); } + bool IsStruct() const { return ValueType->isStruct(); } bool IsNativeStruct() const { return (ValueType->isStruct() && static_cast(ValueType)->isNative); } virtual ExpEmit Emit(VMFunctionBuilder *build); @@ -1541,8 +1543,9 @@ class FxArrayElement : public FxExpression bool AddressRequested; bool AddressWritable; bool arrayispointer = false; + bool noboundscheck; - FxArrayElement(FxExpression*, FxExpression*); + FxArrayElement(FxExpression*, FxExpression*, bool = false); ~FxArrayElement(); FxExpression *Resolve(FCompileContext&); bool RequestAddress(FCompileContext &ctx, bool *writable); @@ -2000,7 +2003,26 @@ class FxForLoop : public FxLoopStatement FxForLoop(FxExpression *init, FxExpression *condition, FxExpression *iteration, FxExpression *code, const FScriptPosition &pos); ~FxForLoop(); FxExpression *DoResolve(FCompileContext&); - ExpEmit Emit(VMFunctionBuilder *build); + ExpEmit Emit(VMFunctionBuilder* build); +}; + +//========================================================================== +// +// FxForLoop +// +//========================================================================== + +class FxForEachLoop : public FxLoopStatement +{ + FName loopVarName; + FxExpression* Array; + FxExpression* Array2; + FxExpression* Code; + +public: + FxForEachLoop(FName vn, FxExpression* arrayvar, FxExpression* arrayvar2, FxExpression* code, const FScriptPosition& pos); + ~FxForEachLoop(); + FxExpression* DoResolve(FCompileContext&); }; //========================================================================== diff --git a/src/common/scripting/frontend/zcc-parse.lemon b/src/common/scripting/frontend/zcc-parse.lemon index aab1f53771c..27d74fc87fe 100644 --- a/src/common/scripting/frontend/zcc-parse.lemon +++ b/src/common/scripting/frontend/zcc-parse.lemon @@ -1829,6 +1829,7 @@ statement(X) ::= compound_statement(A). { X = A; /*X-overwrites-A*/ } statement(X) ::= expression_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } statement(X) ::= selection_statement(X). statement(X) ::= iteration_statement(X). +statement(X) ::= array_iteration_statement(X). statement(X) ::= jump_statement(X). statement(X) ::= assign_statement(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } statement(X) ::= local_var(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } @@ -1986,6 +1987,17 @@ iteration_statement(X) ::= FOR(T) LPAREN for_init(IN) SEMICOLON opt_expr(EX) SEM X = wrap; } +%type array_iteration_statement{ZCC_Statement *} + +array_iteration_statement(X) ::= FOREACH(T) LPAREN variable_name(IN) COLON expr(EX) RPAREN statement(ST). +{ + NEW_AST_NODE(ArrayIterationStmt, iter, T); + iter->ItName = IN; + iter->ItArray = EX; + iter->LoopStatement = ST; + X = iter; +} + while_or_until(X) ::= WHILE(T). { X.Int = ZCC_WHILE; diff --git a/src/common/scripting/frontend/zcc_compile.cpp b/src/common/scripting/frontend/zcc_compile.cpp index 52bbd55f28c..b6fa2340a4d 100644 --- a/src/common/scripting/frontend/zcc_compile.cpp +++ b/src/common/scripting/frontend/zcc_compile.cpp @@ -3179,6 +3179,17 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast, bool substitute) return new FxIfStatement(ConvertNode(iff->Condition), truePath, falsePath, *ast); } + case AST_ArrayIterationStmt: + { + auto iter = static_cast(ast); + auto var = iter->ItName->Name; + FxExpression* const itArray = ConvertNode(iter->ItArray); + FxExpression* const itArray2 = ConvertNode(iter->ItArray); // the handler needs two copies of this - here's the easiest place to create them. + FxExpression* const body = ConvertImplicitScopeNode(ast, iter->LoopStatement); + return new FxForEachLoop(iter->ItName->Name, itArray, itArray2, body, *ast); + + } + case AST_IterationStmt: { auto iter = static_cast(ast); diff --git a/src/common/scripting/frontend/zcc_parser.cpp b/src/common/scripting/frontend/zcc_parser.cpp index 22b531a764c..be771fd84b3 100644 --- a/src/common/scripting/frontend/zcc_parser.cpp +++ b/src/common/scripting/frontend/zcc_parser.cpp @@ -240,6 +240,7 @@ static void InitTokenMap() TOKENDEF (TK_Return, ZCC_RETURN); TOKENDEF (TK_Do, ZCC_DO); TOKENDEF (TK_For, ZCC_FOR); + TOKENDEF (TK_ForEach, ZCC_FOREACH); TOKENDEF (TK_While, ZCC_WHILE); TOKENDEF (TK_Until, ZCC_UNTIL); TOKENDEF (TK_If, ZCC_IF); @@ -1123,6 +1124,18 @@ ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool c break; } + case AST_ArrayIterationStmt: + { + TreeNodeDeepCopy_Start(ArrayIterationStmt); + + // ZCC_IterationStmt + copy->ItName = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ItName, true, copiedNodesList)); + copy->LoopStatement = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->LoopStatement, true, copiedNodesList)); + copy->ItArray = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->ItArray, true, copiedNodesList)); + + break; + } + case AST_IfStmt: { TreeNodeDeepCopy_Start(IfStmt); diff --git a/src/common/scripting/frontend/zcc_parser.h b/src/common/scripting/frontend/zcc_parser.h index b48ecbe3c89..d9710dd95ed 100644 --- a/src/common/scripting/frontend/zcc_parser.h +++ b/src/common/scripting/frontend/zcc_parser.h @@ -138,6 +138,7 @@ enum EZCCTreeNodeType AST_FlagDef, AST_MixinDef, AST_MixinStmt, + AST_ArrayIterationStmt, NUM_AST_NODE_TYPES }; @@ -492,6 +493,13 @@ struct ZCC_IterationStmt : ZCC_Statement enum { Start, End } CheckAt; }; +struct ZCC_ArrayIterationStmt : ZCC_Statement +{ + ZCC_VarName* ItName; + ZCC_Expression* ItArray; + ZCC_Statement* LoopStatement; +}; + struct ZCC_IfStmt : ZCC_Statement { ZCC_Expression *Condition; diff --git a/wadsrc/static/zscript.txt b/wadsrc/static/zscript.txt index 7ab36d78548..a9671701fdf 100644 --- a/wadsrc/static/zscript.txt +++ b/wadsrc/static/zscript.txt @@ -1,4 +1,4 @@ -version "4.9" +version "4.10" // Generic engine code #include "zscript/engine/base.zs"