You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

521 lines
13 KiB

/**
* @file lang_pawn.cpp
* Special functions for pawn stuff
*
* @author Ben Gardner
* @license GPL v2+
*/
#include "lang_pawn.h"
#include "prototypes.h"
using namespace uncrustify;
/**
* Checks to see if a token continues a statement to the next line.
* We need to check for 'open' braces/paren/etc because the level doesn't
* change until the token after the open.
*/
static bool pawn_continued(Chunk *pc, size_t br_level);
/**
* Functions prototypes and definitions can only appear in level 0.
*
* Function prototypes start with "native", "forward", or are just a function
* with a trailing semicolon instead of a open brace (or something else)
*
* somefunc(params) <-- def
* stock somefunc(params) <-- def
* somefunc(params); <-- proto
* forward somefunc(params) <-- proto
* native somefunc[rect](params) <-- proto
*
* Functions start with 'stock', 'static', 'public', or '@' (on level 0)
*
* Variable definitions start with 'stock', 'static', 'new', or 'public'.
*/
static Chunk *pawn_process_line(Chunk *start);
//! We are on a level 0 function proto of def
static Chunk *pawn_mark_function0(Chunk *start, Chunk *fcn);
/**
* follows a variable definition at level 0 until the end.
* Adds a semicolon at the end, if needed.
*/
static Chunk *pawn_process_variable(Chunk *start);
static Chunk *pawn_process_func_def(Chunk *pc);
Chunk *pawn_add_vsemi_after(Chunk *pc)
{
LOG_FUNC_ENTRY();
if (pc->IsSemicolon())
{
return(pc);
}
Chunk *next = pc->GetNextNc();
if ( next->IsNotNullChunk()
&& next->IsSemicolon())
{
return(pc);
}
Chunk chunk = *pc;
chunk.SetType(CT_VSEMICOLON);
chunk.SetParentType(CT_NONE);
chunk.Str() = options::mod_pawn_semicolon() ? ";" : "";
chunk.SetColumn(pc->GetColumn() + pc->Len());
LOG_FMT(LPVSEMI, "%s: Added VSEMI on line %zu, prev='%s' [%s]\n",
__func__, pc->GetOrigLine(), pc->Text(),
get_token_name(pc->GetType()));
return(chunk.CopyAndAddAfter(pc));
}
void pawn_scrub_vsemi()
{
constexpr static auto LCURRENT = LPVSEMI;
LOG_FUNC_ENTRY();
log_rule_B("mod_pawn_semicolon");
if (!options::mod_pawn_semicolon())
{
return;
}
for (Chunk *pc = Chunk::GetHead(); pc->IsNotNullChunk(); pc = pc->GetNext())
{
if (pc->IsNot(CT_VSEMICOLON))
{
continue;
}
Chunk *prev = pc->GetPrevNcNnl();
if (prev->Is(CT_BRACE_CLOSE))
{
if ( prev->GetParentType() == CT_IF
|| prev->GetParentType() == CT_ELSE
|| prev->GetParentType() == CT_SWITCH
|| prev->GetParentType() == CT_CASE
|| prev->GetParentType() == CT_WHILE_OF_DO)
{
pc->Str().clear();
}
}
}
}
static bool pawn_continued(Chunk *pc, size_t br_level)
{
LOG_FUNC_ENTRY();
if (pc->IsNullChunk())
{
return(false);
}
if ( pc->GetLevel() > br_level
|| pc->Is(CT_ARITH)
|| pc->Is(CT_SHIFT)
|| pc->Is(CT_CARET)
|| pc->Is(CT_QUESTION)
|| pc->Is(CT_BOOL)
|| pc->Is(CT_ASSIGN)
|| pc->Is(CT_COMMA)
|| pc->Is(CT_COMPARE)
|| pc->Is(CT_IF)
|| pc->Is(CT_ELSE)
|| pc->Is(CT_DO)
|| pc->Is(CT_SWITCH)
|| pc->Is(CT_WHILE)
|| pc->Is(CT_BRACE_OPEN)
|| pc->Is(CT_VBRACE_OPEN)
|| pc->Is(CT_FPAREN_OPEN)
|| pc->GetParentType() == CT_IF
|| pc->GetParentType() == CT_ELSE
|| pc->GetParentType() == CT_ELSEIF
|| pc->GetParentType() == CT_DO
|| pc->GetParentType() == CT_FOR
|| pc->GetParentType() == CT_SWITCH
|| pc->GetParentType() == CT_WHILE
|| pc->GetParentType() == CT_FUNC_DEF
|| pc->GetParentType() == CT_ENUM
|| pc->GetFlags().test_any(PCF_IN_ENUM | PCF_IN_STRUCT)
|| pc->IsString(":")
|| pc->IsString("+")
|| pc->IsString("-"))
{
return(true);
}
return(false);
} // pawn_continued
void pawn_prescan()
{
LOG_FUNC_ENTRY();
/*
* Start at the beginning and step through the entire file, and clean up
* any questionable stuff
*/
bool did_nl = true;
Chunk *pc = Chunk::GetHead();
while (pc->IsNotNullChunk())
{
if ( did_nl
&& pc->IsNot(CT_PREPROC)
&& !pc->IsNewline()
&& pc->GetLevel() == 0)
{
// pc now points to the start of a line
pc = pawn_process_line(pc);
}
// note that continued lines are ignored
if (pc->IsNotNullChunk())
{
did_nl = (pc->Is(CT_NEWLINE));
}
pc = pc->GetNextNc();
}
}
static Chunk *pawn_process_line(Chunk *start)
{
LOG_FUNC_ENTRY();
//LOG_FMT(LSYS, "%s: %d - %s\n", __func__,
// start->GetOrigLine(), start->Text());
if ( start->Is(CT_NEW)
|| start->IsString("const"))
{
return(pawn_process_variable(start));
}
// if a open paren is found before an assign, then this is a function
Chunk *fcn = Chunk::NullChunkPtr;
if (start->Is(CT_WORD))
{
fcn = start;
}
Chunk *pc = start;
while ( ((pc = pc->GetNextNc())->IsNotNullChunk())
&& !pc->IsString("(")
&& pc->IsNot(CT_ASSIGN)
&& pc->IsNot(CT_NEWLINE))
{
if ( pc->GetLevel() == 0
&& ( pc->Is(CT_FUNCTION)
|| pc->Is(CT_WORD)
|| pc->Is(CT_OPERATOR_VAL)))
{
fcn = pc;
}
}
if (pc->IsNotNullChunk())
{
if (pc->Is(CT_ASSIGN))
{
return(pawn_process_variable(pc));
}
}
if (fcn->IsNotNullChunk())
{
//LOG_FMT(LSYS, "FUNCTION: %s\n", fcn->Text());
return(pawn_mark_function0(start, fcn));
}
if (start->Is(CT_ENUM))
{
pc = start->GetNextType(CT_BRACE_CLOSE, start->GetLevel());
return(pc);
}
//LOG_FMT(LSYS, "%s: Don't understand line %d, starting with '%s' [%s]\n",
// __func__, start->GetOrigLine(), start->Text(), get_token_name(start->GetType()));
return(start);
} // pawn_process_line
static Chunk *pawn_process_variable(Chunk *start)
{
LOG_FUNC_ENTRY();
Chunk *pc = Chunk::NullChunkPtr;
if (start->IsNotNullChunk())
{
pc = start;
}
Chunk *prev = Chunk::NullChunkPtr;
while ((pc = pc->GetNextNc())->IsNotNullChunk())
{
if ( pc->Is(CT_NEWLINE)
&& prev->IsNotNullChunk()
&& !pawn_continued(prev, start->GetLevel()))
{
if (!prev->IsSemicolon())
{
pawn_add_vsemi_after(prev);
}
break;
}
prev = pc;
}
return(pc);
}
void pawn_add_virtual_semicolons()
{
LOG_FUNC_ENTRY();
// Add Pawn virtual semicolons
if (language_is_set(LANG_PAWN))
{
Chunk *prev = Chunk::NullChunkPtr;
Chunk *pc = Chunk::GetHead();
while ((pc = pc->GetNext())->IsNotNullChunk())
{
if ( !pc->IsCommentOrNewline()
&& !pc->IsVBrace())
{
prev = pc;
}
if ( prev->IsNullChunk()
|| ( pc->IsNot(CT_NEWLINE)
&& !pc->IsBraceClose()))
{
continue;
}
// we just hit a newline and we have a previous token
if ( !prev->TestFlags(PCF_IN_PREPROC)
&& !prev->GetFlags().test_any(PCF_IN_ENUM | PCF_IN_STRUCT)
&& !prev->IsSemicolon()
&& !pawn_continued(prev, prev->GetBraceLevel()))
{
pawn_add_vsemi_after(prev);
prev = Chunk::NullChunkPtr;
}
}
}
} // pawn_add_virtual_semicolons
static Chunk *pawn_mark_function0(Chunk *start, Chunk *fcn)
{
LOG_FUNC_ENTRY();
// handle prototypes
if (start == fcn)
{
Chunk *last = fcn->GetNextType(CT_PAREN_CLOSE, fcn->GetLevel())->GetNext();
if (last->Is(CT_SEMICOLON))
{
LOG_FMT(LPFUNC, "%s: %zu] '%s' proto due to semicolon\n",
__func__, fcn->GetOrigLine(), fcn->Text());
fcn->SetType(CT_FUNC_PROTO);
return(last);
}
}
else
{
if ( start->Is(CT_FORWARD)
|| start->Is(CT_NATIVE))
{
LOG_FMT(LPFUNC, "%s: %zu] '%s' [%s] proto due to %s\n",
__func__, fcn->GetOrigLine(), fcn->Text(),
get_token_name(fcn->GetType()),
get_token_name(start->GetType()));
fcn->SetType(CT_FUNC_PROTO);
return(fcn->GetNextNc());
}
}
// Not a prototype, so it must be a function def
return(pawn_process_func_def(fcn));
}
static Chunk *pawn_process_func_def(Chunk *pc)
{
LOG_FUNC_ENTRY();
// We are on a function definition
pc->SetType(CT_FUNC_DEF);
LOG_FMT(LPFUNC, "%s: %zu:%zu %s\n",
__func__, pc->GetOrigLine(), pc->GetOrigCol(), pc->Text());
/*
* If we don't have a brace open right after the close fparen, then
* we need to add virtual braces around the function body.
*/
Chunk *clp = pc->GetNextString(")", 1, 0);
Chunk *last = clp->GetNextNcNnl();
if (last->IsNotNullChunk())
{
LOG_FMT(LPFUNC, "%s: %zu] last is '%s' [%s]\n",
__func__, last->GetOrigLine(), last->Text(), get_token_name(last->GetType()));
}
// See if there is a state clause after the function
if ( last->IsNotNullChunk()
&& last->IsString("<"))
{
LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle open %s\n",
__func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType()));
last->SetType(CT_ANGLE_OPEN);
last->SetParentType(CT_FUNC_DEF);
while ( ((last = last->GetNext())->IsNotNullChunk())
&& !last->IsString(">"))
{
// do nothing just search, TODO: use search_chunk
}
if (last->IsNotNullChunk())
{
LOG_FMT(LPFUNC, "%s: %zu] '%s' has state angle close %s\n",
__func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType()));
last->SetType(CT_ANGLE_CLOSE);
last->SetParentType(CT_FUNC_DEF);
}
last = last->GetNextNcNnl();
}
if (last->IsNullChunk())
{
return(last);
}
if (last->Is(CT_BRACE_OPEN))
{
last->SetParentType(CT_FUNC_DEF);
last = last->GetNextType(CT_BRACE_CLOSE, last->GetLevel());
if (last->IsNotNullChunk())
{
last->SetParentType(CT_FUNC_DEF);
}
}
else
{
LOG_FMT(LPFUNC, "%s: %zu] '%s' fdef: expected brace open: %s\n",
__func__, pc->GetOrigLine(), pc->Text(), get_token_name(last->GetType()));
// do not insert a vbrace before a preproc
if (last->TestFlags(PCF_IN_PREPROC))
{
return(last);
}
Chunk chunk = *last;
chunk.Str().clear();
chunk.SetType(CT_VBRACE_OPEN);
chunk.SetParentType(CT_FUNC_DEF);
Chunk *prev = chunk.CopyAndAddBefore(last);
last = prev;
// find the next newline at level 0
prev = prev->GetNextNcNnl();
do
{
LOG_FMT(LPFUNC, "%s:%zu] check %s, level %zu\n",
__func__, prev->GetOrigLine(), get_token_name(prev->GetType()), prev->GetLevel());
if ( prev->Is(CT_NEWLINE)
&& prev->GetLevel() == 0)
{
Chunk *next = prev->GetNextNcNnl();
if ( next->IsNotNullChunk()
&& next->IsNot(CT_ELSE)
&& next->IsNot(CT_WHILE_OF_DO))
{
break;
}
}
prev->SetLevel(prev->GetLevel() + 1);
prev->SetBraceLevel(prev->GetBraceLevel() + 1);
last = prev;
} while ((prev = prev->GetNext())->IsNotNullChunk());
if (last->IsNotNullChunk())
{
LOG_FMT(LPFUNC, "%s:%zu] ended on %s, level %zu\n",
__func__, last->GetOrigLine(), get_token_name(last->GetType()), last->GetLevel());
}
chunk = *last;
chunk.Str().clear();
chunk.SetType(CT_VBRACE_CLOSE);
chunk.SetParentType(CT_FUNC_DEF);
chunk.SetColumn(chunk.GetColumn() + last->Len());
chunk.SetLevel(0);
chunk.SetBraceLevel(0);
last = chunk.CopyAndAddAfter(last);
}
return(last);
} // pawn_process_func_def
Chunk *pawn_check_vsemicolon(Chunk *pc)
{
LOG_FUNC_ENTRY();
// Grab the open VBrace
Chunk *vb_open = pc->GetPrevType(CT_VBRACE_OPEN);
/*
* Grab the item before the newline
* Don't do anything if:
* - the only thing previous is the V-Brace open
* - in a preprocessor
* - level > (vb_open->GetLevel() + 1) -- ie, in () or []
* - it is something that needs a continuation
* + arith, assign, bool, comma, compare
*/
Chunk *prev = pc->GetPrevNcNnl();
if ( prev->IsNullChunk()
|| prev == vb_open
|| prev->TestFlags(PCF_IN_PREPROC)
|| pawn_continued(prev, vb_open->GetLevel() + 1))
{
if (prev->IsNotNullChunk())
{
LOG_FMT(LPVSEMI, "%s: no VSEMI on line %zu, prev='%s' [%s]\n",
__func__, prev->GetOrigLine(), prev->Text(), get_token_name(prev->GetType()));
}
return(pc);
}
return(pawn_add_vsemi_after(prev));
}