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
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));
|
|
}
|