Skip to content

Commit

Permalink
New compiler: Initializer functions for dynamic structs
Browse files Browse the repository at this point in the history
  • Loading branch information
fernewelten committed Sep 9, 2024
1 parent 6617e0a commit e658971
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 28 deletions.
1 change: 1 addition & 0 deletions Compiler/script2/cc_symboltable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
6 changes: 2 additions & 4 deletions Compiler/script2/cc_symboltable.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Symbol> &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<size_t> const &dims, AGS::Vartype vartype);
Expand Down
79 changes: 70 additions & 9 deletions Compiler/script2/cs_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");

Expand Down
5 changes: 4 additions & 1 deletion Compiler/script2/cs_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 30 additions & 0 deletions Compiler/test2/cc_bytecode_test_1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

165 changes: 151 additions & 14 deletions Compiler/test2/cc_parser_test_1.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
Expand Down

0 comments on commit e658971

Please sign in to comment.