khanat-code-old/code/ryzom/tools/pd_parser/templatizer.cpp
2016-12-09 16:04:26 +01:00

556 lines
10 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "templatizer.h"
bool isBlocStart(CTemplatizerParser t) { return (t.isValid() && t[0] == '{' && t[1] == '{'); }
bool isBlocEnd(CTemplatizerParser t) { return (t.isValid() && t[0] == '}' && t[1] == '}'); }
enum TTemplatizerToken
{
BlocStart,
BlocEnd,
OpenParenth,
CloseParenth,
Equal,
Comma,
Arobace,
Dollar,
Quote,
CommentStart,
CommentEnd,
Identifier,
ListIdentifier,
Unknown
};
struct SToken
{
TTemplatizerToken Token;
const char* Text;
};
SToken SimpleTokens[] =
{
{ BlocStart, "{" },
{ BlocEnd, "}" },
{ OpenParenth, "(" },
{ CloseParenth, ")" },
{ Equal, "=" },
{ Arobace, "@" },
{ Dollar, "$" },
{ Comma, "," },
{ Quote, "\"" },
{ CommentStart, "/*" },
{ CommentEnd, "*/" },
};
CTemplatizerParser skipSpace(CTemplatizerParser t)
{
while (t.isValid() && *t != '\0' && isspace(*t))
++t;
return t;
}
const char* skipSpace(const char* t)
{
while (t != NULL && *t != '\0' && isspace(*t))
++t;
return t;
}
CTemplatizerParser match(const char* keyword, CTemplatizerParser match)
{
while (*keyword != '\0' && *match != '\0' && *keyword == *match)
{
++keyword;
++match;
}
return *keyword == '\0' ? match : CTemplatizerParser();
}
TTemplatizerToken getToken(CTemplatizerParser& t, bool skipspc, std::string* value = NULL)
{
if (skipspc)
t = skipSpace(t);
if (!t.isValid())
return Unknown;
uint i;
CTemplatizerParser result;
for (i=0; i<sizeof(SimpleTokens)/sizeof(SimpleTokens[0]); ++i)
{
result = match(SimpleTokens[i].Text, t);
if (result.isValid())
{
t = result;
return SimpleTokens[i].Token;
}
}
if (isalpha(*t))
{
TTemplatizerToken retToken = Identifier;
if (value != NULL)
value->clear();
do
{
while (isalpha(*t))
{
if (value != NULL)
*value += *t;
++t;
}
t = skipSpace(t);
if (*t != EnvSeparator)
break;
retToken = ListIdentifier;
if (value != NULL)
*value += *t;
++t;
}
while (true);
return retToken;
}
return Unknown;
}
bool popToken(CTemplatizerParser& t, TTemplatizerToken token, bool skipspc, std::string* value = NULL)
{
CTemplatizerParser save = t;
if (getToken(save, skipspc, value) == token)
{
t = save;
return true;
}
return false;
}
bool isNextToken(CTemplatizerParser t, TTemplatizerToken token, bool skipspc, std::string* value = NULL)
{
return getToken(t, skipspc, value) == token;
}
/*
* Destructor
*/
CTemplatizerEnv::~CTemplatizerEnv()
{
clear();
}
// Clear Env
void CTemplatizerEnv::clear()
{
TEnvMap::iterator ite;
for (ite=Envs.begin(); ite!=Envs.end(); ++ite)
delete (*ite).second;
TValueMap::iterator itv;
for (itv=Values.begin(); itv!=Values.end(); ++itv)
delete (*itv).second;
Envs.clear();
Values.clear();
}
/*
* Evaluate string (string replacement)
*/
std::string CTemplatizerEnv::eval(const std::string& text)
{
std::string res;
const char* ptr = text.c_str();
while (*ptr != '\0')
{
if (ptr[0] == '$' && ptr[1] == '(')
{
ptr += 2;
std::string var;
ptr = skipSpace(ptr);
while (isalpha(*ptr) || *ptr == '/' || *ptr == '.')
var += *(ptr++);
while (*ptr != '\0' && *ptr != ')')
++ptr;
if (*ptr == ')')
++ptr;
res += get(var);
}
else if (*ptr != '\r')
{
res += *(ptr++);
}
}
return res;
}
/*
* Constructor
*/
ITemplatizerBloc::ITemplatizerBloc()
{
}
/*
* Destructor
*/
ITemplatizerBloc::~ITemplatizerBloc()
{
uint i;
for (i=0; i<Blocs.size(); ++i)
delete Blocs[i];
TParamMap::iterator it;
for (it=Params.begin(); it!=Params.end(); ++it)
delete (*it).second;
}
/*
* Constructor
*/
CTemplatizer::CTemplatizer()
{
RootBloc = NULL;
RootEnv = NULL;
}
/*
* Destructor
*/
CTemplatizer::~CTemplatizer()
{
if (RootBloc != NULL)
delete RootBloc;
if (RootEnv != NULL)
delete RootEnv;
}
/*
* Build templatizer from text
*/
bool CTemplatizer::build(const char* text)
{
CTemplatizerParser parser(text);
RootBloc = ITemplatizerBloc::parseBloc(parser);
RootEnv = new CTemplatizerEnv(NULL);
return (RootBloc != NULL);
}
/**
* Evaluate template and render to string
*/
std::string CTemplatizer::eval()
{
if (RootBloc != NULL && RootEnv != NULL)
return RootBloc->eval(RootEnv);
else
return "";
}
/*
* Parse bloc
*/
ITemplatizerBloc* ITemplatizerBloc::parseBloc(CTemplatizerParser& ptr)
{
std::string blocType;
ITemplatizerBloc* bloc = NULL;
if (popToken(ptr, Identifier, true, &blocType) || popToken(ptr, ListIdentifier, true, &blocType))
{
if (blocType == "root") bloc = new CTemplatizerRootBloc();
else if (blocType == "sub") bloc = new CTemplatizerSubBloc();
else if (blocType == "loop") bloc = new CTemplatizerLoopBloc();
else if (blocType == "ifdefenv") bloc = new CTemplatizerIfDefEnvBloc();
else if (blocType == "ifdef") bloc = new CTemplatizerIfDefBloc();
else if (blocType == "ifnotdefenv") bloc = new CTemplatizerIfNotDefEnvBloc();
else if (blocType == "ifnotdef") bloc = new CTemplatizerIfNotDefBloc();
else if (blocType == "switch") bloc = new CTemplatizerSwitchBloc();
else if (blocType == "file") bloc = new CTemplatizerFileBloc();
else if (blocType == "set") bloc = new CTemplatizerSetBloc();
else if (blocType == "append") bloc = new CTemplatizerAppendBloc();
else if (blocType == "define") bloc = new CTemplatizerDefineBloc();
else if (blocType == "if") bloc = new CTemplatizerIfBloc();
else if (blocType == "ifnot") bloc = new CTemplatizerIfNotBloc();
else if (blocType == "join") bloc = new CTemplatizerJoinBloc();
else if (blocType == "class") bloc = new CTemplatizerClassBloc();
else if (blocType == "object") bloc = new CTemplatizerObjectBloc();
else if (blocType == "ref") bloc = new CTemplatizerReferenceBloc();
else if (blocType == "refenv") bloc = new CTemplatizerRefEnvBloc();
else if (blocType == "breakpoint") bloc = new CTemplatizerBreakpointBloc();
else bloc = new CTemplatizerUserFunctionBloc(blocType);
if (bloc == NULL)
{
nlwarning("Templatizer: failed to decode bloc '%s' at line %d", blocType.c_str(), ptr.getLine());
return NULL;
}
ptr = bloc->parseHeader(ptr);
if (!ptr.isValid())
{
nlwarning("Templatizer: failed to decode header of bloc '%s' at line %d", blocType.c_str(), ptr.getLine());
delete bloc;
return NULL;
}
if (bloc->hasInternal())
{
if (!popToken(ptr, BlocStart, true))
{
nlwarning("Templatizer: failed to decode start of bloc '%s' at line %d", blocType.c_str(), ptr.getLine());
delete bloc;
return NULL;
}
ptr = bloc->parseInternal(ptr);
if (!ptr.isValid())
{
nlwarning("Templatizer: failed to parse bloc '%s' at line %d", blocType.c_str(), ptr.getLine());
delete bloc;
return NULL;
}
if (!popToken(ptr, BlocEnd, true))
{
nlwarning("Templatizer: failed to decode end of bloc '%s' at line %d", blocType.c_str(), ptr.getLine());
delete bloc;
return NULL;
}
}
}
else if (isNextToken(ptr, CommentStart, true))
{
bloc = new CTemplatizerCommentBloc();
ptr = bloc->parseInternal(ptr);
if (!ptr.isValid())
nlwarning("Templatizer: failed to parse bloc 'Comment' at line %d", ptr.getLine());
}
else if (isNextToken(ptr, Quote, true))
{
bloc = new CTemplatizerTextBloc();
ptr = bloc->parseInternal(ptr);
if (!ptr.isValid())
nlwarning("Templatizer: failed to parse bloc 'Text' at line %d", ptr.getLine());
}
if (!ptr.isValid())
{
delete bloc;
return NULL;
}
return bloc;
}
/*
* Parse bloc header
*/
CTemplatizerParser ITemplatizerBloc::parseHeader(CTemplatizerParser ptr)
{
if (popToken(ptr, OpenParenth, true))
{
uint currentDefArg = 0;
const char** args = getDefParamList();
if (popToken(ptr, CloseParenth, true))
return ptr;
do
{
std::string paramName;
if (!popToken(ptr, Identifier, true, &paramName))
{
if (args == NULL || args[currentDefArg] == NULL)
{
ptr.invalidate();
return ptr;
}
paramName = args[currentDefArg++];
}
else
{
if (!popToken(ptr, Equal, true))
{
ptr.invalidate();
return ptr;
}
}
ITemplatizerBloc* bloc = parseBloc(ptr);
if (bloc == NULL)
{
ptr.invalidate();
return ptr;
}
Params[paramName] = bloc;
if (!popToken(ptr, Comma, true))
break;
}
while (true);
if (!popToken(ptr, CloseParenth, true))
{
ptr.invalidate();
return ptr;
}
}
return ptr;
}
/*
* Parse bloc internal data
*/
CTemplatizerParser ITemplatizerBloc::parseInternal(CTemplatizerParser ptr)
{
ITemplatizerBloc* bloc = NULL;
do
{
bloc = parseBloc(ptr);
if (bloc != NULL)
Blocs.push_back(bloc);
}
while (bloc != NULL && *ptr != '\0' && !isBlocEnd(ptr));
return ptr;
}
/*
* Parse bloc internal data
*/
CTemplatizerParser CTemplatizerTextBloc::parseInternal(CTemplatizerParser ptr)
{
Text.clear();
ptr = skipSpace(ptr);
if (*ptr != '"')
return CTemplatizerParser();
++ptr;
while (*ptr != '\0' && *ptr != '"')
{
if (*ptr == '\\' && ptr[1] != '\0')
{
++ptr;
switch (*ptr)
{
case 'n':
Text += '\n';
break;
case 't':
Text += '\t';
break;
case '\r':
++ptr;
if (*ptr == '\n')
++ptr;
break;
case '\n':
++ptr;
if (*ptr == '\r')
++ptr;
break;
default:
Text += *ptr;
break;
}
++ptr;
}
else
{
if (*ptr != '\r')
Text += *ptr;
++ptr;
}
}
if (*ptr != '"')
return CTemplatizerParser();
return ++ptr;
}
/*
* Parse bloc internal data
*/
CTemplatizerParser CTemplatizerCommentBloc::parseInternal(CTemplatizerParser ptr)
{
ptr = skipSpace(ptr);
if (!popToken(ptr, CommentStart, true))
return NULL;
while (*ptr != '\0' && !isNextToken(ptr, CommentEnd, false))
++ptr;
if (!popToken(ptr, CommentEnd, false))
{
ptr.invalidate();
return ptr;
}
return ptr;
}