Skip to content

Commit

Permalink
- added 'foreach' loop to ZScript.
Browse files Browse the repository at this point in the history
Syntax:

foreach(variable : array)
{
}

the variable's type is automatically deducted.
  • Loading branch information
coelckers committed Nov 15, 2022
1 parent 4994e11 commit 29b4418
Show file tree
Hide file tree
Showing 8 changed files with 173 additions and 24 deletions.
2 changes: 1 addition & 1 deletion src/common/engine/sc_man_scanner.re
Expand Up @@ -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); }
Expand Down
123 changes: 103 additions & 20 deletions src/common/scripting/backend/codegen.cpp
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
26 changes: 24 additions & 2 deletions src/common/scripting/backend/codegen.h
Expand Up @@ -272,6 +272,7 @@ enum EFxType
EFX_WhileLoop,
EFX_DoWhileLoop,
EFX_ForLoop,
EFX_ForEachLoop,
EFX_JumpStatement,
EFX_ReturnStatement,
EFX_ClassTypeCast,
Expand Down Expand Up @@ -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<PStruct*>(ValueType)->isNative); }

virtual ExpEmit Emit(VMFunctionBuilder *build);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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&);
};

//==========================================================================
Expand Down
12 changes: 12 additions & 0 deletions src/common/scripting/frontend/zcc-parse.lemon
Expand Up @@ -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*/ }
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions src/common/scripting/frontend/zcc_compile.cpp
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions src/common/scripting/frontend/zcc_parser.cpp
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
8 changes: 8 additions & 0 deletions src/common/scripting/frontend/zcc_parser.h
Expand Up @@ -138,6 +138,7 @@ enum EZCCTreeNodeType
AST_FlagDef,
AST_MixinDef,
AST_MixinStmt,
AST_ArrayIterationStmt,

NUM_AST_NODE_TYPES
};
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion wadsrc/static/zscript.txt
@@ -1,4 +1,4 @@
version "4.9"
version "4.10"

// Generic engine code
#include "zscript/engine/base.zs"
Expand Down

0 comments on commit 29b4418

Please sign in to comment.