- added 'foreach' loop to ZScript.

Syntax:

foreach(variable : array)
{
}

the variable's type is automatically deducted.
This commit is contained in:
Christoph Oelckers 2022-11-15 21:29:04 +01:00
parent dc9451d584
commit 6b3e57fd2c
9 changed files with 174 additions and 25 deletions

View file

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

View file

@ -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)
if (!noboundscheck) // this is 'foreach' which is known to be inside the bounds.
{
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 (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)
@ -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)
{
ScriptPosition.Message(MSG_ERROR, "Cannot initialize non-scalar variable %s here", Name.GetChars());
delete this;
return nullptr;
}
SAFE_RESOLVE(Init, ctx);
ValueType = Init->ValueType;
if (ValueType->RegType == REGT_NIL)
{
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;
}
}
// 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;

View file

@ -272,6 +272,7 @@ enum EFxType
EFX_WhileLoop,
EFX_DoWhileLoop,
EFX_ForLoop,
EFX_ForEachLoop,
EFX_JumpStatement,
EFX_ReturnStatement,
EFX_ClassTypeCast,
@ -349,6 +350,7 @@ public:
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<PStruct*>(ValueType)->isNative); }
virtual ExpEmit Emit(VMFunctionBuilder *build);
@ -1541,8 +1543,9 @@ public:
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 @@ public:
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&);
};
//==========================================================================

View file

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

View file

@ -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<ZCC_ArrayIterationStmt*>(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<ZCC_IterationStmt *>(ast);

View file

@ -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<ZCC_VarName*>(TreeNodeDeepCopy_Internal(ast, origCasted->ItName, true, copiedNodesList));
copy->LoopStatement = static_cast<ZCC_Statement*>(TreeNodeDeepCopy_Internal(ast, origCasted->LoopStatement, true, copiedNodesList));
copy->ItArray = static_cast<ZCC_Expression*>(TreeNodeDeepCopy_Internal(ast, origCasted->ItArray, true, copiedNodesList));
break;
}
case AST_IfStmt:
{
TreeNodeDeepCopy_Start(IfStmt);

View file

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

View file

@ -49,7 +49,7 @@ const char *GetVersionString();
#define RC_PRODUCTVERSION2 VERSIONSTR
// These are for content versioning.
#define VER_MAJOR 4
#define VER_MINOR 9
#define VER_MINOR 10
#define VER_REVISION 0
#define ENG_MAJOR 1

View file

@ -1,4 +1,4 @@
version "4.9"
version "4.10"
#include "zscript/engine/base.zs"
#include "zscript/engine/dynarrays.zs"
#include "zscript/engine/inputevents.zs"