diff --git a/Compiler/script2/cc_symboltable.cpp b/Compiler/script2/cc_symboltable.cpp index 1bea83387a..28ef368d12 100644 --- a/Compiler/script2/cc_symboltable.cpp +++ b/Compiler/script2/cc_symboltable.cpp @@ -731,6 +731,7 @@ AGS::Symbol AGS::SymbolTable::FindStructComponent(Symbol strct, Symbol const com for (auto components_it = components.begin(); components_it != components.end(); components_it++) if (component == components_it->first) return components_it->second; + strct = entries.at(strct).VartypeD->Parent; } return kKW_NoSymbol; } diff --git a/Compiler/script2/cc_symboltable.h b/Compiler/script2/cc_symboltable.h index 79fbbd9715..8b2a6813ae 100644 --- a/Compiler/script2/cc_symboltable.h +++ b/Compiler/script2/cc_symboltable.h @@ -475,11 +475,9 @@ struct SymbolTable : public SymbolTableConstant // Fills compo_list with the symbols of all the strct components. Includes the ancestors' components void GetComponentsOfStruct(Symbol strct, std::vector &compo_list) const; // Find the description of a component. - // Return nullptr if not found. Otherwise, caller must 'delete' the result after being done with it - // Start search with the components of ancestor. Symbol FindStructComponent(Symbol strct, Symbol component, Symbol ancestor) const; - inline Symbol *FindStructComponent(Symbol strct, Symbol component) const { FindStructComponent(strct, component, strct); } - + inline Symbol FindStructComponent(Symbol strct, Symbol component) const { return FindStructComponent(strct, component, strct); } + // Arrays and variables that are arrays // The "Array[...] of vartype" vartype Vartype VartypeWithArray(std::vector const &dims, AGS::Vartype vartype); diff --git a/Compiler/script2/cs_parser.cpp b/Compiler/script2/cs_parser.cpp index 8aec2afbd5..7dff632997 100644 --- a/Compiler/script2/cs_parser.cpp +++ b/Compiler/script2/cs_parser.cpp @@ -1890,6 +1890,67 @@ void AGS::Parser::StripOutermostParens(SrcList &expression) } } +void AGS::Parser::ParseExpression_New_InitFuncCall(Symbol argument_vartype, SrcList &expression) +{ + if (kKW_OpenParenthesis != expression.PeekNext()) + { + Symbol const init_sym = _sym.Find("initialize"); + if (kKW_NoSymbol == init_sym || !_sym.IsStructVartype(argument_vartype)) + return; // nothing to do + + Symbol const init_function = + _sym.FindStructComponent(argument_vartype, init_sym); + if (!_sym.IsFunction(init_function)) + return; + + UserError( + ReferenceMsgSym( + "Expected the parameter list for function '%s'", + init_function).c_str(), + _sym.GetName(init_function).c_str() + ); + } + + do // exactly 1 times + { + Symbol const init_sym = _sym.Find("initialize"); + if (kKW_NoSymbol == init_sym || !_sym.IsStructVartype(argument_vartype)) + break; + if (!_sym.IsStructVartype(argument_vartype)) + break; + Symbol const init_function = + _sym.FindStructComponent(argument_vartype, init_sym); + if (kKW_NoSymbol == init_function) + break; + if (!_sym.IsFunction(init_function)) + break; + Symbol const frv = _sym.FuncReturnVartype(init_function); + // 'int' is a special accomodation for functions that are declared with the + // 'function' keyword. This is why we check for exactly 'int' here; + // the compiler is not fine with any other integer vartypes + if (kKW_Void != frv && kKW_Int != frv) + UserError( + ReferenceMsgSym( + "The return type of function '%s' must be 'void' but is '%s'", + init_function).c_str(), + _sym.GetName(init_function).c_str(), + _sym.GetName(frv).c_str()); + + PushReg(SREG_AX); + // Address of the new object is expected in MAR + WriteCmd(SCMD_REGTOREG, SREG_AX, SREG_MAR); + _reg_track.SetRegister(SREG_MAR); + EvaluationResult eres_dummy; + AccessData_FunctionCall(init_function, expression, eres_dummy); + PopReg(SREG_AX); + return; + } while (false); + + // Here when there isn't any init function: must have '()' following + SkipNextSymbol(expression, kKW_OpenParenthesis); + Expect(kKW_CloseParenthesis, expression.GetNext()); +} + void AGS::Parser::ParseExpression_New(SrcList &expression, EvaluationResult &eres) { expression.StartRead(); @@ -1945,14 +2006,6 @@ void AGS::Parser::ParseExpression_New(SrcList &expression, EvaluationResult &ere if (!is_managed) UserError("Expected '[' after the non-managed type '%s'", _sym.GetName(argument_vartype).c_str()); - if (kKW_OpenParenthesis == expression.PeekNext()) - { - Warning("'()' after 'new' isn't implemented, is currently ignored"); - expression.GetNext(); - expression.SkipToCloser(); - SkipNextSymbol(_src, kKW_CloseParenthesis); - } - // Only do this check for new, not for new[]. if (0u == _sym.GetSize(argument_vartype)) UserError( @@ -1989,6 +2042,9 @@ void AGS::Parser::ParseExpression_New(SrcList &expression, EvaluationResult &ere _reg_track.SetRegister(SREG_AX); + if (!with_bracket_expr) + ParseExpression_New_InitFuncCall(argument_vartype, expression); + ParseExpression_CheckUsedUp(expression); eres.Type = eres.kTY_RunTimeValue; @@ -2594,7 +2650,12 @@ void AGS::Parser::AccessData_FunctionCall_ProvideDefaults(int func_args_count, s { Symbol const param_default = _sym[func_symbol].FunctionD->Parameters[arg_idx].Default; if (kKW_NoSymbol == param_default) - UserError("Function call parameter #%d isn't provided and doesn't have any default value", arg_idx); + UserError( + ReferenceMsgSym( + "Function call parameter #%d for function '%s' isn't provided and doesn't have any default value", + func_symbol).c_str(), + arg_idx, + _sym.GetName(func_symbol).c_str()); if (!_sym.IsLiteral(param_default)) InternalError("Parameter default symbol isn't literal"); diff --git a/Compiler/script2/cs_parser.h b/Compiler/script2/cs_parser.h index bf2892de65..d0d6103874 100644 --- a/Compiler/script2/cs_parser.h +++ b/Compiler/script2/cs_parser.h @@ -83,7 +83,7 @@ class Parser // Get the label of a function, in order to insert it into the code, // this label will be replaced by its value later on - inline CodeCell Function2Label(Symbol func) { return func * _size + _kind; } + inline CodeCell Function2Label(Symbol func) const { return func * _size + _kind; } // Keep track of the location of a label that needs to be replaced later on inline void TrackLabelLoc(CodeLoc loc) { _scrip.Labels.push_back(loc); } @@ -578,6 +578,9 @@ class Parser // Return whether this is possible. bool ParseExpression_CompileTime(Symbol op_sym, EvaluationResult const &eres_lhs, EvaluationResult const &eres_rhs, EvaluationResult &eres); + // Process a parenthesised list following a 'new' + void ParseExpression_New_InitFuncCall(Symbol arg_vartype, SrcList &expression); + // Parse the term given in 'expression'. The lowest-binding operator is unary 'new' // Parsing must use up 'expression' completely; if there are trailing symbols, throw 'UserError'. void ParseExpression_New(SrcList &expression, EvaluationResult &eres); diff --git a/Compiler/test2/cc_bytecode_test_1.cpp b/Compiler/test2/cc_bytecode_test_1.cpp index 5fd6c0cdb6..0493f682e4 100644 --- a/Compiler/test2/cc_bytecode_test_1.cpp +++ b/Compiler/test2/cc_bytecode_test_1.cpp @@ -3559,3 +3559,33 @@ TEST_F(Bytecode1, Linenum02) EXPECT_EQ(stringssize, scrip.strings.size()); } +TEST_F(Bytecode1, ParensAfterNew) { + + char const *inpl = "\ + managed struct Ancester \n\ + { \n\ + import int initialize(float); \n\ + int Payload1; \n\ + }; \n\ + \n\ + managed struct Struct \n\ + extends Ancester \n\ + { \n\ + int Payload2; \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct s = new Struct(7.0); \n\ + return s.Payload2 + s.Payload1; \n\ + } \n\ + "; + + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + EXPECT_EQ(0u, mh.WarningsCount()); + ASSERT_STREQ("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); + + // WriteOutput("ParensAfterNew", scrip); +} + diff --git a/Compiler/test2/cc_parser_test_1.cpp b/Compiler/test2/cc_parser_test_1.cpp index b04594c191..4178d0ef38 100644 --- a/Compiler/test2/cc_parser_test_1.cpp +++ b/Compiler/test2/cc_parser_test_1.cpp @@ -2474,29 +2474,166 @@ TEST_F(Compile1, ReportMissingFunction) { EXPECT_NE(std::string::npos, err_msg.find("pNZaFLjz3ajd")); } -TEST_F(Compile1, ParensAfterNew) { +TEST_F(Compile1, ParensAfterNew1) { - // Function is called, but not defined with body or external - // This should be flagged naming the function + // Parentheses after 'new' are okay + // even when there isn't any initializer char const *inpl = "\ - managed struct Struct \n\ - { \n\ - int Payload; \n\ - }; \n\ - \n\ - int game_start() \n\ - { \n\ + managed struct Struct \n\ + { \n\ + int Payload; \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ Struct *s = new Struct(); \n\ - } \n\ + } \n\ + "; + + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + size_t err_line = mh.GetError().Lineno; + EXPECT_EQ(0u, mh.WarningsCount()); + + ASSERT_STREQ("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); +} + +TEST_F(Compile1, ParensAfterNew2) { + + // When there is an initializer, then it must be called + + char const *inpl = "\ + managed struct Struct \n\ + { \n\ + import int initialize(); \n\ + int Payload; \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct *s = new Struct; \n\ + } \n\ "; + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + size_t err_line = mh.GetError().Lineno; + EXPECT_EQ(0u, mh.WarningsCount()); + + ASSERT_STRNE("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); + ASSERT_NE(std::string::npos, err_msg.find("parameter list")); +} + +TEST_F(Compile1, ParensAfterNew3) { + + // When there is an initializer, then it must be called + + char const *inpl = "\ + managed struct Ancester \n\ + { \n\ + import int initialize(); \n\ + int Payload; \n\ + }; \n\ + \n\ + managed struct Struct \n\ + extends Ancester \n\ + { \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct *s = new Struct; \n\ + } \n\ + "; - AGS::MessageHandler mh; - int compile_result = cc_compile(inpl, 0u, scrip, mh); + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); std::string err_msg = mh.GetError().Message; size_t err_line = mh.GetError().Lineno; - EXPECT_EQ(1u, mh.WarningsCount()); + EXPECT_EQ(0u, mh.WarningsCount()); + + ASSERT_STRNE("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); + ASSERT_NE(std::string::npos, err_msg.find("Ancester::initialize")); +} + +TEST_F(Compile1, ParensAfterNew4) { + + // When there is an initializer, + // then it must be called with the proper arguments + + char const *inpl = "\ + managed struct Struct \n\ + { \n\ + int Payload; \n\ + import void initialize(float); \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct *s = new Struct(); \n\ + } \n\ + "; + + + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + size_t err_line = mh.GetError().Lineno; + EXPECT_EQ(0u, mh.WarningsCount()); + + ASSERT_STRNE("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); + EXPECT_NE(std::string::npos, err_msg.find("parameter")); +} + +TEST_F(Compile1, ParensAfterNew5) { + + // When there is an initializer, + // it must have return type 'void' or 'int' + + char const *inpl = "\ + managed struct Struct \n\ + { \n\ + import float initialize(); \n\ + int Payload; \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct *s = new Struct(); \n\ + } \n\ + "; + + + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + size_t err_line = mh.GetError().Lineno; + EXPECT_EQ(0u, mh.WarningsCount()); + + ASSERT_STRNE("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); +} + +TEST_F(Compile1, ParensAfterNew6) { + + // A non-function component 'initialize' is fine + // and isn't treated as an initializer function + + char const *inpl = "\ + managed struct Struct \n\ + { \n\ + float initialize; \n\ + int Payload; \n\ + }; \n\ + \n\ + int game_start() \n\ + { \n\ + Struct *s = new Struct(); \n\ + } \n\ + "; + + + int compile_result = cc_compile(inpl, kNoOptions, scrip, mh); + std::string err_msg = mh.GetError().Message; + size_t err_line = mh.GetError().Lineno; + EXPECT_EQ(0u, mh.WarningsCount()); ASSERT_STREQ("Ok", mh.HasError() ? err_msg.c_str() : "Ok"); }