diff --git a/src/common/scripting/backend/codegen.cpp b/src/common/scripting/backend/codegen.cpp index 6cf593fa2a..5cb384636f 100644 --- a/src/common/scripting/backend/codegen.cpp +++ b/src/common/scripting/backend/codegen.cpp @@ -2718,6 +2718,75 @@ ExpEmit FxMultiAssign::Emit(VMFunctionBuilder *build) return LocalVarContainer->Emit(build); } + +//========================================================================== +// +// +// +//========================================================================== + +FxMultiAssignDecl::FxMultiAssignDecl(FArgumentList &base, FxExpression *right, const FScriptPosition &pos) + :FxExpression(EFX_MultiAssign, pos) +{ + Base = std::move(base); + Right = right; +} + +//========================================================================== +// +// +// +//========================================================================== + +FxMultiAssignDecl::~FxMultiAssignDecl() +{ + SAFE_DELETE(Right); +} + +//========================================================================== +// +// +// +//========================================================================== + +FxExpression *FxMultiAssignDecl::Resolve(FCompileContext &ctx) +{ + CHECKRESOLVED(); + SAFE_RESOLVE(Right, ctx); + if (Right->ExprType != EFX_VMFunctionCall) + { + Right->ScriptPosition.Message(MSG_ERROR, "Function call expected on right side of multi-assigment"); + delete this; + return nullptr; + } + auto VMRight = static_cast(Right); + auto rets = VMRight->GetReturnTypes(); + if (Base.Size() == 1) + { + Right->ScriptPosition.Message(MSG_ERROR, "Multi-assignment with only one element in function %s", VMRight->Function->SymbolName.GetChars()); + delete this; + return nullptr; + } + if (rets.Size() < Base.Size()) + { + Right->ScriptPosition.Message(MSG_ERROR, "Insufficient returns in function %s", VMRight->Function->SymbolName.GetChars()); + delete this; + return nullptr; + } + FxSequence * DeclAndAssign = new FxSequence(ScriptPosition); + const unsigned int n = Base.Size(); + for (unsigned int i = 0; i < n; i++) + { + assert(Base[i]->ExprType == EFX_Identifier); + DeclAndAssign->Add(new FxLocalVariableDeclaration(rets[i], ((FxIdentifier*)Base[i])->Identifier, nullptr, 0, Base[i]->ScriptPosition)); + } + DeclAndAssign->Add(new FxMultiAssign(Base, Right, ScriptPosition)); + Right = nullptr; + delete this; + return DeclAndAssign->Resolve(ctx); +} + + //========================================================================== // // diff --git a/src/common/scripting/backend/codegen.h b/src/common/scripting/backend/codegen.h index 8a156a5295..7612db7981 100644 --- a/src/common/scripting/backend/codegen.h +++ b/src/common/scripting/backend/codegen.h @@ -904,6 +904,17 @@ public: ExpEmit Emit(VMFunctionBuilder *build); }; +class FxMultiAssignDecl : public FxExpression +{ + FArgumentList Base; + FxExpression *Right; +public: + FxMultiAssignDecl(FArgumentList &base, FxExpression *right, const FScriptPosition &pos); + ~FxMultiAssignDecl(); + FxExpression *Resolve(FCompileContext&); + //ExpEmit Emit(VMFunctionBuilder *build); This node is transformed into Declarations + FxMultiAssign , so it won't ever be emitted itself +}; + //========================================================================== // // FxAssignSelf diff --git a/src/common/scripting/frontend/ast.cpp b/src/common/scripting/frontend/ast.cpp index 44cc0f516c..50e1c2e9f1 100644 --- a/src/common/scripting/frontend/ast.cpp +++ b/src/common/scripting/frontend/ast.cpp @@ -816,6 +816,15 @@ static void PrintAssignStmt(FLispString &out, const ZCC_TreeNode *node) out.Close(); } +static void PrintAssignDeclStmt(FLispString &out, const ZCC_TreeNode *node) +{ + ZCC_AssignDeclStmt *snode = (ZCC_AssignDeclStmt *)node; + out.Open("assign-stmt-decl"); + PrintNodes(out, snode->Dests); + PrintNodes(out, snode->Sources); + out.Close(); +} + static void PrintLocalVarStmt(FLispString &out, const ZCC_TreeNode *node) { ZCC_LocalVarStmt *snode = (ZCC_LocalVarStmt *)node; @@ -989,6 +998,7 @@ static const NodePrinterFunc TreeNodePrinter[] = PrintSwitchStmt, PrintCaseStmt, PrintAssignStmt, + PrintAssignDeclStmt, PrintLocalVarStmt, PrintFuncParamDecl, PrintConstantDef, diff --git a/src/common/scripting/frontend/zcc-parse.lemon b/src/common/scripting/frontend/zcc-parse.lemon index 560f15ad86..1a9146f372 100644 --- a/src/common/scripting/frontend/zcc-parse.lemon +++ b/src/common/scripting/frontend/zcc-parse.lemon @@ -1840,6 +1840,7 @@ 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) ::= assign_decl_statement(A) SEMICOLON.{ X = A; /*X-overwrites-A*/ } statement(X) ::= local_var(A) SEMICOLON. { X = A; /*X-overwrites-A*/ } statement(X) ::= error SEMICOLON. { X = NULL; } statement(X) ::= staticarray_statement(A). { X = A; /*X-overwrites-A*/ } @@ -2098,6 +2099,17 @@ assign_statement(X) ::= LBRACKET expr_list(A) RBRACKET EQ expr(B). [EQ] X = stmt; } +%type assign_decl_statement{ZCC_AssignDeclStmt *} + +assign_decl_statement(X) ::= LET LBRACKET identifier_list(A) RBRACKET EQ expr(B). [EQ] +{ + NEW_AST_NODE(AssignDeclStmt,stmt,A); + stmt->AssignOp = ZCC_EQ; + stmt->Dests = A; + stmt->Sources = B; + X = stmt; +} + /*----- Local Variable Definition "Statements" -----*/ %type local_var{ZCC_LocalVarStmt *} diff --git a/src/common/scripting/frontend/zcc_compile.cpp b/src/common/scripting/frontend/zcc_compile.cpp index 7777b73279..4c8b54792c 100644 --- a/src/common/scripting/frontend/zcc_compile.cpp +++ b/src/common/scripting/frontend/zcc_compile.cpp @@ -3350,6 +3350,28 @@ FxExpression *ZCCCompiler::ConvertNode(ZCC_TreeNode *ast, bool substitute) return new FxMultiAssign(args, ConvertNode(ass->Sources), *ast); } + case AST_AssignDeclStmt: + { + auto ass = static_cast(ast); + FArgumentList args; + { + ZCC_TreeNode *n = ass->Dests; + if(n) do + { + args.Push(new FxIdentifier(static_cast(n)->Id,*n)); + n = n->SiblingNext; + } while(n != ass->Dests); + } + assert(ass->Sources->SiblingNext == ass->Sources); // right side should be a single function call - nothing else + if (ass->Sources->NodeType != AST_ExprFuncCall) + { + // don't let this through to the code generator. This node is only used to assign multiple returns of a function to more than one variable. + Error(ass, "Right side of multi-assignment must be a function call"); + return new FxNop(*ast); // allow compiler to continue looking for errors. + } + return new FxMultiAssignDecl(args, ConvertNode(ass->Sources), *ast); + } + default: break; } diff --git a/src/common/scripting/frontend/zcc_parser.cpp b/src/common/scripting/frontend/zcc_parser.cpp index 2343e49093..f1a3e8d1fe 100644 --- a/src/common/scripting/frontend/zcc_parser.cpp +++ b/src/common/scripting/frontend/zcc_parser.cpp @@ -1195,6 +1195,18 @@ ZCC_TreeNode *TreeNodeDeepCopy_Internal(ZCC_AST *ast, ZCC_TreeNode *orig, bool c break; } + case AST_AssignDeclStmt: + { + TreeNodeDeepCopy_Start(AssignDeclStmt); + + // ZCC_AssignDeclStmt + copy->Dests = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Dests, true, copiedNodesList)); + copy->Sources = static_cast(TreeNodeDeepCopy_Internal(ast, origCasted->Sources, true, copiedNodesList)); + copy->AssignOp = origCasted->AssignOp; + + break; + } + case AST_LocalVarStmt: { TreeNodeDeepCopy_Start(LocalVarStmt); diff --git a/src/common/scripting/frontend/zcc_parser.h b/src/common/scripting/frontend/zcc_parser.h index 7d2cce2e80..69276b768c 100644 --- a/src/common/scripting/frontend/zcc_parser.h +++ b/src/common/scripting/frontend/zcc_parser.h @@ -122,6 +122,7 @@ enum EZCCTreeNodeType AST_SwitchStmt, AST_CaseStmt, AST_AssignStmt, + AST_AssignDeclStmt, AST_LocalVarStmt, AST_FuncParamDecl, AST_ConstantDef, @@ -533,6 +534,13 @@ struct ZCC_AssignStmt : ZCC_Statement int AssignOp; }; +struct ZCC_AssignDeclStmt : ZCC_Statement +{ + ZCC_Identifier *Dests; + ZCC_Expression *Sources; + int AssignOp; +}; + struct ZCC_LocalVarStmt : ZCC_Statement { ZCC_Type *Type;