From 9af7931a79501c192c48944dded818f8a728e00e Mon Sep 17 00:00:00 2001 From: Arkshine Date: Mon, 21 Jul 2014 21:12:35 +0200 Subject: [PATCH 01/14] Introduce TextParser API. --- amxmodx/AMBuilder | 2 + amxmodx/CTextParsers.cpp | 1113 +++++++++++++++++++++ amxmodx/CTextParsers.h | 90 ++ amxmodx/amxmodx.h | 1 + amxmodx/meta_api.cpp | 2 + amxmodx/modules.cpp | 3 +- amxmodx/msvc10/amxmodx_mm.vcxproj | 3 + amxmodx/msvc10/amxmodx_mm.vcxproj.filters | 20 +- amxmodx/textparse.cpp | 226 +++++ amxmodx/textparse.h | 191 ++++ plugins/include/textparse.inc | 223 +++++ plugins/testsuite/textparse_test.sma | 116 +++ public/ITextParsers.h | 450 +++++++++ 13 files changed, 2438 insertions(+), 2 deletions(-) create mode 100644 amxmodx/CTextParsers.cpp create mode 100644 amxmodx/CTextParsers.h create mode 100644 amxmodx/textparse.cpp create mode 100644 amxmodx/textparse.h create mode 100644 plugins/include/textparse.inc create mode 100644 plugins/testsuite/textparse_test.sma create mode 100644 public/ITextParsers.h diff --git a/amxmodx/AMBuilder b/amxmodx/AMBuilder index 16125cdc..1665ec89 100644 --- a/amxmodx/AMBuilder +++ b/amxmodx/AMBuilder @@ -94,6 +94,8 @@ binary.sources = [ 'CDataPack.cpp', 'datapacks.cpp', 'stackstructs.cpp', + 'CTextParsers.cpp', + 'textparse.cpp', ] if builder.target_platform == 'windows': diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp new file mode 100644 index 00000000..4a3f63b1 --- /dev/null +++ b/amxmodx/CTextParsers.cpp @@ -0,0 +1,1113 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#include "amxmodx.h" +#include "CTextParsers.h" +/* +#include +#include +#include +#include +#include +#include +#include */ + +TextParsers g_TextParser; +ITextParsers *textparsers = &g_TextParser; + +static int g_ini_chartable1[255] = { 0 }; +static int g_ws_chartable[255] = { 0 }; + +bool TextParsers::IsWhitespace(const char *stream) +{ + return g_ws_chartable[(unsigned char)*stream] == 1; +} + +TextParsers::TextParsers() +{ + g_ini_chartable1[(unsigned)'_'] = 1; + g_ini_chartable1[(unsigned)'-'] = 1; + g_ini_chartable1[(unsigned)','] = 1; + g_ini_chartable1[(unsigned)'+'] = 1; + g_ini_chartable1[(unsigned)'.'] = 1; + g_ini_chartable1[(unsigned)'$'] = 1; + g_ini_chartable1[(unsigned)'?'] = 1; + g_ini_chartable1[(unsigned)'/'] = 1; + g_ws_chartable[(unsigned)'\n'] = 1; + g_ws_chartable[(unsigned)'\v'] = 1; + g_ws_chartable[(unsigned)'\r'] = 1; + g_ws_chartable[(unsigned)'\t'] = 1; + g_ws_chartable[(unsigned)'\f'] = 1; + g_ws_chartable[(unsigned)' '] = 1; +} + +/* +void TextParsers::OnSourceModAllInitialized() +{ + sharesys->AddInterface(NULL, this); +}*/ + +unsigned int TextParsers::GetUTF8CharBytes(const char *stream) +{ + return _GetUTF8CharBytes(stream); +} + +/** +* File streams +*/ + +bool FileStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read) +{ + size_t num = fread(buffer, 1, maxlength, (FILE *)stream); + + *read = static_cast(num); + + if (num == 0 && feof((FILE *)stream)) + { + return true; + } + + return (ferror((FILE *)stream) == 0); +} + +SMCError TextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, SMCStates *states) +{ + FILE *fp = fopen(file, "rt"); + + if (!fp) + { + if (states != NULL) + { + states->line = 0; + states->col = 0; + } + return SMCError_StreamOpen; + } + + SMCError result = ParseStream_SMC(fp, FileStreamReader, smc, states); + + fclose(fp); + + return result; +} + +SMCError TextParsers::ParseSMCFile(const char *file, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize) +{ + const char *errstr; + FILE *fp = fopen(file, "rt"); + + if (fp == NULL) + { + char error[256] = "unknown"; + if (states != NULL) + { + states->line = 0; + states->col = 0; + } + /*libsys->GetPlatformError(error, sizeof(error));*/ + snprintf(buffer, maxsize, "File could not be opened: %s", error); + return SMCError_StreamOpen; + } + + SMCError result = ParseStream_SMC(fp, FileStreamReader, smc_listener, states); + + fclose(fp); + + errstr = GetSMCErrorString(result); + snprintf(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); + + return result; +} + +struct RawStream +{ + const char *stream; + size_t length; + size_t pos; +}; + +bool RawStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read) +{ + RawStream *rs = (RawStream *)stream; + + if (rs->pos >= rs->length) + { + return false; + } + + size_t remaining = rs->length - rs->pos; + + /* Use the smaller of the two */ + size_t copy = (remaining > maxlength) ? maxlength : remaining; + + memcpy(buffer, &rs->stream[rs->pos], copy); + rs->pos += copy; + *read = copy; + assert(rs->pos <= rs->length); + + return true; +} + +SMCError TextParsers::ParseSMCStream(const char *stream, + size_t length, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize) +{ + RawStream rs; + SMCError result; + + rs.stream = stream; + rs.length = length; + rs.pos = 0; + + result = ParseStream_SMC(&rs, RawStreamReader, smc_listener, states); + + const char *errstr = GetSMCErrorString(result); + snprintf(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); + + return result; +} + +/** +* Raw parsing of streams with helper functions +*/ + +struct StringInfo +{ + StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { } + bool quoted; + char *ptr; + char *end; + bool special; +}; + +const char *FixupString(StringInfo &data) +{ + if (!data.ptr) + { + return NULL; + } + + if (data.quoted) + { + data.ptr++; + } +#if defined _DEBUG + else { + /* A string will never have beginning whitespace because we ignore it in the stream. + * Furthermore, if there is trailing whitespace, the end ptr will point to it, so it is valid + * to overwrite! Lastly, the last character must be whitespace or a comment/invalid character. + */ + } +#endif + + /* Do some extra work on strings that have special quoted characters. */ + if (data.special) + { + char *outptr = data.ptr; + size_t len = data.end - data.ptr; + if (len >= 2) + { + for (size_t i = 0; i= 0; i--) + { + if (info[i].ptr) + { + return info[i].ptr; + } + } + + return NULL; +} + +SMCError TextParsers::ParseStream_SMC(void *stream, + STREAMREADER srdr, + ITextListener_SMC *smc, + SMCStates *pStates) +{ + char *reparse_point = NULL; + char in_buf[4096]; + char *parse_point = in_buf; + char *line_begin = in_buf; + unsigned int read; + unsigned int curlevel = 0; + bool in_quote = false; + bool ignoring = false; + bool eol_comment = false; + bool ml_comment = false; + unsigned int i; + SMCError err = SMCError_Okay; + SMCResult res; + SMCStates states; + char c; + + StringInfo strings[3]; + StringInfo emptystring; + + states.line = 1; + states.col = 0; + + smc->ReadSMC_ParseStart(); + + /** + * The stream reader reads in as much as it can fill the buffer with. + * It then processes the buffer. If the buffer cannot be fully processed, for example, + * a line is left hanging with no newline, then the contents of the buffer is shifted + * down, and the buffer is filled from the stream reader again. + * + * What makes this particularly annoying is that we cache pointers everywhere, so when + * the shifting process takes place, all those pointers must be shifted as well. + */ + while (srdr(stream, parse_point, sizeof(in_buf)-(parse_point - in_buf) - 1, &read)) + { + if (!read) + { + break; + } + + /* Check for BOM markings, which is only relevant on the first line. + * Not worth it, but it could be moved out of the loop. + */ + if (states.line == 1 && + in_buf[0] == (char)0xEF && + in_buf[1] == (char)0xBB && + in_buf[2] == (char)0xBF) + { + /* Move EVERYTHING down :\ */ + memmove(in_buf, &in_buf[3], read - 3); + read -= 3; + } + + if (reparse_point) + { + read += (parse_point - reparse_point); + parse_point = reparse_point; + reparse_point = NULL; + } + + for (i = 0; iReadSMC_RawLine(&states, line_begin)) != SMCResult_Continue) + { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; + goto failed; + } + parse_point[i] = '\n'; + + /* Now we check the sanity of our staged strings! */ + if (strings[2].ptr) + { + if (!curlevel) + { + err = SMCError_InvalidProperty1; + goto failed; + } + /* Assume the next string is a property and pass the info on. */ + if ((res = smc->ReadSMC_KeyValue( + &states, + FixupString(strings[2]), + FixupString(strings[1]))) != SMCResult_Continue) + { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; + goto failed; + } + scrap(strings); + } + + /* Change the states for the next line */ + states.col = 0; + states.line++; + line_begin = &parse_point[i + 1]; //Note: safe because this gets relocated later + } + else if (ignoring) + { + if (in_quote) + { + /* If i was 0, we could have reparsed, so make sure there's no buffer underrun */ + if ((&parse_point[i] != in_buf) && c == '"' && parse_point[i - 1] != '\\') + { + /* If we reached a quote in an ignore phase, + * we're staging a string and we must rotate it out. + */ + in_quote = false; + ignoring = false; + /* Set our info */ + strings[0].end = &parse_point[i]; + strings[0].quoted = true; + if (rotate(strings) != NULL) + { + /* If we rotated too many strings, there was too much crap on one line */ + err = SMCError_InvalidTokens; + goto failed; + } + } + else if (c == '\\') + { + strings[0].special = true; + if (i == (read - 1)) + { + reparse_point = &parse_point[i]; + break; + } + } + } + else if (ml_comment) + { + if (c == '*') + { + /* Check if we need to get more input first */ + if (i == read - 1) + { + reparse_point = &parse_point[i]; + break; + } + if (parse_point[i + 1] == '/') + { + ml_comment = false; + ignoring = false; + /* We should not be staging anything right now. */ + assert(strings[0].ptr == NULL); + /* Advance the input stream so we don't choke on this token */ + i++; + states.col++; + } + } + } + } + else + { + /* Check if we're whitespace or not */ + if (!g_ws_chartable[(unsigned char)c]) + { + bool restage = false; + /* Check various special tokens: + * ; + * // + * / * + * { + * } + */ + if (c == ';' || c == '/') + { + /* If it's a line-based comment (that is, ; or //) + * we will need to scrap everything until the end of the line. + */ + if (c == '/') + { + if (i == read - 1) + { + /* If we reached the end of the look-ahead, we need to re-check our input. + * Breaking out will force this to be the new reparse point! + */ + reparse_point = &parse_point[i]; + break; + } + if (parse_point[i + 1] == '/') + { + /* standard comment */ + ignoring = true; + eol_comment = true; + restage = true; + } + else if (parse_point[i + 1] == '*') + { + /* inline comment - start ignoring */ + ignoring = true; + ml_comment = true; + /* yes, we restage, meaning that: + * STR/ *stuff* /ING (space because ml comments don't nest in C++) + * will not generate 'STRING', but rather 'STR' and 'ING'. + * This should be a rare occurrence and is done here for convenience. + */ + restage = true; + } + } + else + { + ignoring = true; + eol_comment = true; + restage = true; + } + } + else if (c == '{') + { + /* If we are staging a string, we must rotate here */ + if (strings[0].ptr) + { + /* We have unacceptable tokens on this line */ + if (rotate(strings) != NULL) + { + err = SMCError_InvalidSection1; + goto failed; + } + } + /* Sections must always be alone */ + if (strings[2].ptr != NULL) + { + err = SMCError_InvalidSection1; + goto failed; + } + else if (strings[1].ptr == NULL) + { + err = SMCError_InvalidSection2; + goto failed; + } + if ((res = smc->ReadSMC_NewSection(&states, FixupString(strings[1]))) + != SMCResult_Continue) + { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; + goto failed; + } + strings[1] = emptystring; + curlevel++; + } + else if (c == '}') + { + /* Unlike our matching friend, this can be on the same line as something prior */ + if (rotate(strings) != NULL) + { + err = SMCError_InvalidSection3; + goto failed; + } + if (strings[2].ptr) + { + if (!curlevel) + { + err = SMCError_InvalidProperty1; + goto failed; + } + if ((res = smc->ReadSMC_KeyValue( + &states, + FixupString(strings[2]), + FixupString(strings[1]))) + != SMCResult_Continue) + { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; + goto failed; + } + } + else if (strings[1].ptr) + { + err = SMCError_InvalidSection3; + goto failed; + } + else if (!curlevel) + { + err = SMCError_InvalidSection4; + goto failed; + } + /* Now it's safe to leave the section */ + scrap(strings); + if ((res = smc->ReadSMC_LeavingSection(&states)) != SMCResult_Continue) + { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; + goto failed; + } + curlevel--; + } + else if (c == '"') + { + /* If we get a quote mark, we always restage, but we need to do it beforehand */ + if (strings[0].ptr) + { + strings[0].end = &parse_point[i]; + if (rotate(strings) != NULL) + { + err = SMCError_InvalidTokens; + goto failed; + } + } + strings[0].ptr = &parse_point[i]; + in_quote = true; + ignoring = true; + } + else if (!strings[0].ptr) + { + /* If we have no string, we must start one */ + strings[0].ptr = &parse_point[i]; + } + if (restage && strings[0].ptr) + { + strings[0].end = &parse_point[i]; + if (rotate(strings) != NULL) + { + err = SMCError_InvalidTokens; + goto failed; + } + } + } + else + { + /* If we're eating a string and get whitespace, we need to restage. + * (Note that if we are quoted, this is being ignored) + */ + if (strings[0].ptr) + { + /* + * The specification says the second string in a pair does not need to be quoted. + * Thus, we check if there's already a string on the stack. + * If there's a newline, we always rotate so the newline has an empty starter. + */ + if (!strings[1].ptr) + { + /* There's no string, so we must move this one down and eat up another */ + strings[0].end = &parse_point[i]; + rotate(strings); + } + else if (!strings[1].quoted) + { + err = SMCError_InvalidTokens; + goto failed; + } + } + } + } + + /* Advance which token we're on */ + states.col++; + } + + if (line_begin != in_buf) + { + /* The line buffer has advanced, so it's safe to copy N bytes back to the beginning. + * What's N? N is the lowest point we're currently relying on. + */ + char *stage = lowstring(strings); + if (!stage || stage > line_begin) + { + stage = line_begin; + } + unsigned int bytes = read - (stage - parse_point); + + /* It is now safe to delete everything before the staged point */ + memmove(in_buf, stage, bytes); + + /* Calculate the number of bytes in the new buffer */ + bytes = stage - in_buf; + /* Relocate all the cached pointers to our new base */ + line_begin -= bytes; + reloc(strings[0], bytes); + reloc(strings[1], bytes); + reloc(strings[2], bytes); + if (reparse_point) + { + reparse_point -= bytes; + } + if (parse_point) + { + parse_point = &parse_point[read]; + parse_point -= bytes; + } + } + else if (read == sizeof(in_buf)-1) + { + err = SMCError_TokenOverflow; + goto failed; + } + } + + /* If we're done parsing and there are tokens left over... */ + if (curlevel) + { + err = SMCError_InvalidSection5; + goto failed; + } + else if (strings[0].ptr || strings[1].ptr) + { + err = SMCError_InvalidTokens; + goto failed; + } + + smc->ReadSMC_ParseEnd(false, false); + + if (pStates != NULL) + { + *pStates = states; + } + + return SMCError_Okay; + +failed: + if (pStates != NULL) + { + *pStates = states; + } + + smc->ReadSMC_ParseEnd(true, (err == SMCError_Custom)); + + return err; +} + + +/** +* INI parser +*/ + +bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) +{ + FILE *fp = fopen(file, "rt"); + unsigned int curline = 0; + unsigned int curtok; + size_t len; + + if (!fp) + { + if (line) + { + *line = 0; + } + + return false; + } + + char buffer[2048]; + char *ptr, *save_ptr; + bool in_quote; + + while (!feof(fp)) + { + curline++; + curtok = 0; + buffer[0] = '\0'; + if (fgets(buffer, sizeof(buffer), fp) == NULL) + { + break; + } + + //:TODO: this will only run once, so find a nice way to move it out of the while loop + /* If this is the first line, check the first three bytes for BOM */ + if (curline == 1 && + buffer[0] == (char)0xEF && + buffer[1] == (char)0xBB && + buffer[2] == (char)0xBF) + { + /* We have a UTF-8 marked file... skip these bytes */ + ptr = &buffer[3]; + } + else { + ptr = buffer; + } + + /*************************************************** + * We preprocess the string before parsing tokens! * + ***************************************************/ + + /* First strip beginning whitespace */ + while (*ptr != '\0' && g_ws_chartable[(unsigned char)*ptr] != 0) + { + ptr++; + } + + len = strlen(ptr); + + if (!len) + { + continue; + } + + /* Now search for comment characters */ + in_quote = false; + save_ptr = ptr; + for (size_t i = 0; iReadINI_RawLine(ptr, &curtok)) + { + goto event_failed; + } + + if (*ptr == '[') + { + bool invalid_tokens = false; + bool got_bracket = false; + bool extra_tokens = false; + char c; + bool alnum; + wchar_t wc; + + for (size_t i = 1; iReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok)) + { + goto event_failed; + } + } + else { + char *key_ptr = ptr; + char *val_ptr = NULL; + char c; + size_t first_space = 0; + bool invalid_tokens = false; + bool equal_token = false; + bool quotes = false; + bool alnum; + wchar_t wc; + + for (size_t i = 0; iReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok)) + { + curtok = 0; + goto event_failed; + } + } + } + + if (line) + { + *line = curline; + } + + fclose(fp); + + return true; + +event_failed: + if (line) + { + *line = curline; + } + + if (col) + { + *col = curtok; + } + + fclose(fp); + + return false; +} + +const char *TextParsers::GetSMCErrorString(SMCError err) +{ + static const char *s_errors[] = + { + NULL, + "Stream failed to open", + "Stream returned read error", + NULL, + "Un-quoted section has invalid tokens", + "Section declared without header", + "Section declared with unknown tokens", + "Section ending without a matching section beginning", + "Section beginning without a matching ending", + "Line contained too many invalid tokens", + "Token buffer overflowed", + "A property was declared outside of a section", + }; + + if (err < SMCError_Okay || err > SMCError_InvalidProperty1) + { + return NULL; + } + + return s_errors[err]; +} diff --git a/amxmodx/CTextParsers.h b/amxmodx/CTextParsers.h new file mode 100644 index 00000000..45ebe52c --- /dev/null +++ b/amxmodx/CTextParsers.h @@ -0,0 +1,90 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ +#define _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ + +#include +#include + +using namespace SourceMod; + +/** +* @param void * IN: Stream pointer +* @param char * IN/OUT: Stream buffer +* @param size_t IN: Maximum size of buffer +* @param unsigned int * OUT: Number of bytes read (0 = end of stream) +* @return True on success, false on failure +*/ +typedef bool(*STREAMREADER)(void *, char *, size_t, unsigned int *); + +class TextParsers : public ITextParsers +{ +public: + TextParsers(); +public: + bool ParseFile_INI(const char *file, + ITextListener_INI *ini_listener, + unsigned int *line, + unsigned int *col); + + SMCError ParseFile_SMC(const char *file, + ITextListener_SMC *smc_listener, + SMCStates *states); + + SMCError ParseSMCFile(const char *file, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize); + + SMCError ParseSMCStream(const char *stream, + size_t length, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize); + + unsigned int GetUTF8CharBytes(const char *stream); + + const char *GetSMCErrorString(SMCError err); + bool IsWhitespace(const char *stream); +private: + SMCError ParseStream_SMC(void *stream, + STREAMREADER srdr, + ITextListener_SMC *smc, + SMCStates *states); +}; + +extern TextParsers g_TextParser; + +#endif //_INCLUDE_SOURCEMOD_TEXTPARSERS_H_ + diff --git a/amxmodx/amxmodx.h b/amxmodx/amxmodx.h index c23ec57b..6b9973ca 100755 --- a/amxmodx/amxmodx.h +++ b/amxmodx/amxmodx.h @@ -64,6 +64,7 @@ extern AMX_NATIVE_INFO vector_Natives[]; extern AMX_NATIVE_INFO g_SortNatives[]; extern AMX_NATIVE_INFO g_DataStructNatives[]; extern AMX_NATIVE_INFO g_StackNatives[]; +extern AMX_NATIVE_INFO g_TextParserNatives[]; #if defined(_WIN32) #define DLLOAD(path) (DLHANDLE)LoadLibrary(path) diff --git a/amxmodx/meta_api.cpp b/amxmodx/meta_api.cpp index 75693f6d..c54ea4b3 100755 --- a/amxmodx/meta_api.cpp +++ b/amxmodx/meta_api.cpp @@ -30,6 +30,7 @@ #include #include "trie_natives.h" #include "CDataPack.h" +#include "textparse.h" plugin_info_t Plugin_info = { @@ -395,6 +396,7 @@ int C_Spawn(edict_t *pent) g_TrieHandles.clear(); g_TrieSnapshotHandles.clear(); g_DataPackHandles.clear(); + g_TextParsersHandles.clear(); char map_pluginsfile_path[256]; char prefixed_map_pluginsfile[256]; diff --git a/amxmodx/modules.cpp b/amxmodx/modules.cpp index 00639297..3929cb2e 100755 --- a/amxmodx/modules.cpp +++ b/amxmodx/modules.cpp @@ -552,7 +552,8 @@ int set_amxnatives(AMX* amx, char error[128]) amx_Register(amx, trie_Natives, -1); amx_Register(amx, g_DatapackNatives, -1); amx_Register(amx, g_StackNatives, -1); - + amx_Register(amx, g_TextParserNatives, -1); + //we're not actually gonna check these here anymore amx->flags |= AMX_FLAG_PRENIT; diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index 1da903e8..069d2c7a 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -306,6 +306,7 @@ + @@ -335,6 +336,7 @@ All + @@ -370,6 +372,7 @@ + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index 06c8962c..67096bb7 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -173,9 +173,15 @@ Source Files - + Source Files + + + Source Files + + + Source Files @@ -317,6 +323,18 @@ Header Files +<<<<<<< HEAD +======= + + Header Files + + + Header Files + + + Header Files + +>>>>>>> Introduce TextParser API. diff --git a/amxmodx/textparse.cpp b/amxmodx/textparse.cpp new file mode 100644 index 00000000..0ed02579 --- /dev/null +++ b/amxmodx/textparse.cpp @@ -0,0 +1,226 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#include "amxmodx.h" +#include +#include + +TextParserHandles g_TextParsersHandles; + +static cell AMX_NATIVE_CALL SMC_CreateParser(AMX *amx, cell *params) +{ + return static_cast(g_TextParsersHandles.create()); +} + +static cell AMX_NATIVE_CALL SMC_SetParseStart(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length; + const char* functionName = get_amxstring(amx, params[2], 0, length); + int function = registerSPForwardByName(amx, functionName, FP_CELL, FP_DONE); + + if (function == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", functionName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + p->parse_start = function; + + return 1; +} + +static cell AMX_NATIVE_CALL SMC_SetParseEnd(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length; + const char* funcName = get_amxstring(amx, params[2], 0, length); + int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_DONE); + + if (func == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + p->parse_end = func; + + return 1; +} + +static cell AMX_NATIVE_CALL SMC_SetReaders(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length; + const char* NewSectionFuncName = get_amxstring(amx, params[2], 0, length); + const char* KeyValueFuncName = get_amxstring(amx, params[3], 1, length); + const char* EndSectionFuncName = get_amxstring(amx, params[4], 2, length); + + int NewSectionFunc = registerSPForwardByName(amx, NewSectionFuncName, FP_CELL, FP_STRING, FP_DONE); + if (NewSectionFunc == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", NewSectionFuncName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + int KeyValueFunc = registerSPForwardByName(amx, KeyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_DONE); + if (KeyValueFunc == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", KeyValueFuncName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + int EndSectionFunc = registerSPForwardByName(amx, EndSectionFuncName, FP_CELL, FP_DONE); + if (EndSectionFunc == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", EndSectionFuncName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + p->new_section = NewSectionFunc; + p->key_value = KeyValueFunc; + p->end_section = EndSectionFunc; + + return 1; +} + +static cell AMX_NATIVE_CALL SMC_SetRawLine(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length; + const char* funcName = get_amxstring(amx, params[2], 0, length); + + int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_DONE); + if (func == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + p->raw_line = func; + + return 1; +} + +static cell AMX_NATIVE_CALL SMC_ParseFile(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length; + const char *file = build_pathname("%s", get_amxstring(amx, params[2], 0, length)); + + SMCStates states; + SMCError p_err = textparsers->ParseFile_SMC(file, p, &states); + + *get_amxaddr(amx, params[3]) = states.line; + *get_amxaddr(amx, params[4]) = states.col; + + return (cell)p_err; +} + +static cell AMX_NATIVE_CALL SMC_GetErrorString(AMX *amx, cell *params) +{ + const char *str = textparsers->GetSMCErrorString((SMCError)params[1]); + + if (!str) + { + return 0; + } + + return set_amxstring(amx, params[2], str, params[3]); +} + +static cell AMX_NATIVE_CALL SMC_DestroyParser(AMX *amx, cell *params) +{ + cell *ptr = get_amxaddr(amx, params[1]); + + ParseInfo *p = g_TextParsersHandles.lookup(*ptr); + + if (p == NULL) + { + return 0; + } + + if (g_TextParsersHandles.destroy(*ptr)) + { + *ptr = 0; + return 1; + } + + return 0; +} + +AMX_NATIVE_INFO g_TextParserNatives[] = +{ + { "SMC_CreateParser" , SMC_CreateParser }, + { "SMC_ParseFile" , SMC_ParseFile }, + { "SMC_GetErrorString", SMC_GetErrorString }, + { "SMC_SetParseStart" , SMC_SetParseStart }, + { "SMC_SetParseEnd" , SMC_SetParseEnd }, + { "SMC_SetReaders" , SMC_SetReaders }, + { "SMC_SetRawLine" , SMC_SetRawLine }, + { "SMC_DestroyParser" , SMC_DestroyParser }, + { NULL, NULL }, +}; diff --git a/amxmodx/textparse.h b/amxmodx/textparse.h new file mode 100644 index 00000000..cdb3ec02 --- /dev/null +++ b/amxmodx/textparse.h @@ -0,0 +1,191 @@ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ + +#ifndef _INCLUDE_TEXTPARSE_H_ +#define _INCLUDE_TEXTPARSE_H_ + +#include "amxmodx.h" +#include "CTextParsers.h" + +class ParseInfo : public ITextListener_SMC +{ +public: + ParseInfo() + { + parse_start = -1; + parse_end = -1; + new_section = -1; + key_value = -1; + end_section = -1; + raw_line = -1; + handle = -1; + } + ~ParseInfo() {} +public: + void ReadSMC_ParseStart() + { + if (parse_start != -1) + { + executeForwards(parse_start, handle); + } + } + + void ReadSMC_ParseEnd(bool halted, bool failed) + { + if (parse_end != -1) + { + executeForwards(parse_end, handle, halted ? 1 : 0, failed ? 1 : 0); + } + } + + SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name) + { + if (new_section != -1) + { + return (SMCResult)executeForwards(new_section, handle, name); + } + + return SMCResult_Continue; + } + + SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) + { + if (key_value != -1) + { + return (SMCResult)executeForwards(key_value, handle, key, value); + } + + return SMCResult_Continue; + } + + SMCResult ReadSMC_LeavingSection(const SMCStates *states) + { + if (end_section != -1) + { + return (SMCResult)executeForwards(end_section, handle, handle); + } + + return SMCResult_Continue; + } + + SMCResult ReadSMC_RawLine(const SMCStates *states, const char *line) + { + if (raw_line != -1) + { + return (SMCResult)executeForwards(raw_line, handle, line, states->line); + } + + return SMCResult_Continue; + } +public: + int parse_start; + int parse_end; + int new_section; + int key_value; + int end_section; + int raw_line; + int handle; +}; + +template +class TextParserHandles +{ +private: + ke::Vector m_textparsers; + +public: + TextParserHandles() { } + ~TextParserHandles() + { + this->clear(); + } + + void clear() + { + for (size_t i = 0; i < m_textparsers.length(); i++) + { + if (m_textparsers[i] != NULL) + { + delete m_textparsers[i]; + } + } + + m_textparsers.clear(); + } + T *lookup(int handle) + { + handle--; + + if (handle < 0 || handle >= static_cast(m_textparsers.length())) + { + return NULL; + } + + return m_textparsers[handle]; + } + int create() + { + for (size_t i = 0; i < m_textparsers.length(); i++) + { + if (m_textparsers[i] == NULL) + { + // reuse handle + m_textparsers[i] = new T; + + return static_cast(i)+1; + } + } + m_textparsers.append(new T); + return m_textparsers.length(); + } + bool destroy(int handle) + { + handle--; + + if (handle < 0 || handle >= static_cast(m_textparsers.length())) + { + return false; + } + + if (m_textparsers[handle] == NULL) + { + return false; + } + delete m_textparsers[handle]; + m_textparsers[handle] = NULL; + + return true; + } +}; + +extern TextParserHandles g_TextParsersHandles; + +#endif // _INCLUDE_TEXTPARSE_H_ \ No newline at end of file diff --git a/plugins/include/textparse.inc b/plugins/include/textparse.inc new file mode 100644 index 00000000..2ae996c5 --- /dev/null +++ b/plugins/include/textparse.inc @@ -0,0 +1,223 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This file is part of the SourceMod/SourcePawn SDK. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * 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 General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#if defined _textparse_included + #endinput +#endif +#define _textparse_included + + +/** + * Everything below describes the SMC Parse, or "SourceMod Configuration" format. + * This parser is entirely event based. You must hook events to receive data. + * The file format itself is nearly identical to Valve's KeyValues format. + */ + +/** + * Parse result directive. + */ +enum SMCResult +{ + SMCParse_Continue, /**< Continue parsing */ + SMCParse_Halt, /**< Stop parsing here */ + SMCParse_HaltFail /**< Stop parsing and return failure */ +}; + +/** + * Parse error codes. + */ +enum SMCError +{ + SMCError_Okay = 0, /**< No error */ + SMCError_StreamOpen, /**< Stream failed to open */ + SMCError_StreamError, /**< The stream died... somehow */ + SMCError_Custom, /**< A custom handler threw an error */ + SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /**< A section was declared without any header */ + SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /**< A section ending has no matching beginning */ + SMCError_InvalidSection5, /**< A section beginning has no matching ending */ + SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /**< The token buffer overflowed */ + SMCError_InvalidProperty1, /**< A property was declared outside of any section */ +}; + +enum TextParser +{ + Invalid_TextPaser = 0 +}; + +/** + * Creates a new SMC file format parser. + * This is used to set parse hooks. + * + * @return A new Handle to an SMC Parse structure. + */ +native TextParser:SMC_CreateParser(); + +/** + * Parses an SMC file. + * + * @param smc A Handle to an SMC Parse structure. + * @param file A string containing the file path. + * @param line An optional by reference cell to store the last line number read. + * @param col An optional by reference cell to store the last column number read. + + * @return An SMCParseError result. + * @error Invalid or corrupt Handle. + */ +native SMCError:SMC_ParseFile(TextParser:smc, const file[], &line = 0, &col = 0); + +/** + * Gets an error string for an SMCError code. + * + * @note SMCError_Okay returns false. + * @note SMCError_Custom (which is thrown on SMCParse_HaltFail) returns false. + * + * @param error The SMCParseError code. + * @param buffer A string buffer for the error (contents undefined on failure). + * @param buf_max The maximum size of the buffer. + * + * @return True on success, false otherwise. + */ +native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); + +/** + * Sets the SMC_ParseStart function of a parse Handle. + * Below is the prototype of callback.: + * + * - ParseStart: + * Called when parsing is started. + * + * @param smc The SMC Parse Handle. + * @noreturn + * + * public SMC_ParseStart(TextParser:smc) + * + * @param smc Handle to an SMC Parse. + * @param func A ParseStart callback. + * + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SMC_SetParseStart(TextParser:smc, const func[]); + +/** + * Sets the SMC_ParseEnd of a parse handle. + * Below is the prototype of callback.: + * + * - ParseEnd: + * Called when parsing is halted. + * + * @param smc The SMC Parse Handle. + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + * @noreturn + * + * public SMC_ParseEnd(TextParser:smc, bool:halted, bool:failed) + * + * @param smc Handle to an SMC Parse. + * @param func A ParseEnd callback.. + * + * @noreturn + * @error Invalid or corrupt Handle. + */ +native SMC_SetParseEnd(TextParser:smc, const func[]); + +/** + * Sets the three main reader functions. + * Below are the different prototypes of callbacks: + * + * - NewSection: + * Called when the parser finds the end of the current section. + * @note Enclosing quotes are always stripped. + * + * @param smc The SMC Parse Handle. + * @param name String containing section name. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_NewSection(TextParser:smc, const name[]) + * + * - KeyValue: + * Called when the parser finds a new key/value pair. + * @note Enclosing quotes are always stripped. + * + * @param smc The SMC Parse Handle. + * @param key String containing key name. + * @param value String containing value name. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_KeyValue(TextParser:smc, const key[], const value[]) + * + * - EndSection: + * Called when the parser finds the end of the current section. + * + * @param smc The SMC Parse Handle. + * + * public SMCResult:SMC_EndSection(TextParser:smc) + * - + * @param smc An SMC parse Handle. + * @param ns A NewSection callback. + * @param kv A KeyValue callback. + * @param es An EndSection callback. + * + * @noreturn + */ +native SMC_SetReaders(TextParser:smc, const nsFunc[], const kvFunc[], const esFunc[]); + +/** + * Sets a raw line reader on an SMC parser Handle. + * Below is the prototype of callback.: + * + * - RawLine: + * Called whenever a raw line is read. + * + * @param smc The SMC Parse Handle. + * @param line A string containing the raw line from the file. + * @param lineno The line number it occurs on. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_RawLine(TextParser:smc, const line[], lineno) + * + * @param smc Handle to an SMC Parse. + * @param func A RawLine callback.. + * @noreturn + */ +native SMC_SetRawLine(TextParser:smc, const func[]); + +/** + * Disposes of a text parser. + * + * @param smc Handle to an SMC Parse. + * @return True if disposed, false otherwise. + */ +native SMC_DestroyParser(&TextParser:smc); \ No newline at end of file diff --git a/plugins/testsuite/textparse_test.sma b/plugins/testsuite/textparse_test.sma new file mode 100644 index 00000000..d5ab402c --- /dev/null +++ b/plugins/testsuite/textparse_test.sma @@ -0,0 +1,116 @@ +#include +#include + +new SuccessCount; +new Trie:ExpectedKVData; + +public plugin_init() +{ + register_concmd("textparse_vdf", "ConsoleCommand_TextParseVDF"); + register_clcmd("textparse_ini", "ServerCommand_TextParseINI"); +} + +/** + * VDF Config format + */ + +public ConsoleCommand_TextParseVDF() +{ + server_print("Testing text parser with VDF config file format..."); + + new const configFile[] = "addons/amxmodx/scripting/testsuite/textparse_test.cfg"; + + ExpectedKVData = TrieCreate(); + TrieSetString(ExpectedKVData, "Logging", "on"); + TrieSetString(ExpectedKVData, "LogMode", "daily"); + TrieSetString(ExpectedKVData, "ServerLang", "en"); + TrieSetString(ExpectedKVData, "PublicChatTrigger", "!"); + TrieSetString(ExpectedKVData, "SilentChatTrigger", "/"); + TrieSetString(ExpectedKVData, "SilentFailSuppress", "no"); + TrieSetString(ExpectedKVData, "PassInfoVar", "_password"); + TrieSetString(ExpectedKVData, "MenuItemSound", "buttons/button14.wav"); + TrieSetString(ExpectedKVData, "MenuExitSound", "buttons/combine_button7.wav"); + TrieSetString(ExpectedKVData, "MenuExitBackSound", "buttons/combine_button7.wav"); + TrieSetString(ExpectedKVData, "AllowClLanguageVar", "On"); + TrieSetString(ExpectedKVData, "DisableAutoUpdate", "no"); + TrieSetString(ExpectedKVData, "ForceRestartAfterUpdate", "no"); + TrieSetString(ExpectedKVData, "AutoUpdateURL", "http://update.sourcemod.net/update/"); + TrieSetString(ExpectedKVData, "DebugSpew", "no"); + TrieSetString(ExpectedKVData, "SteamAuthstringValidation", "yes"); + TrieSetString(ExpectedKVData, "BlockBadPlugins", "yes"); + TrieSetString(ExpectedKVData, "SlowScriptTimeout", "8"); + + new const expectedSectionCount = 2; // Include start and end. + new const expectedStartEndCount = 2; + new const expectedKeyValueCount = TrieGetSize(ExpectedKVData); + new const expectedLineCount = file_size(configFile, .flag = 1) - 1; + + new TextParser:parser = SMC_CreateParser(); + + SMC_SetReaders(parser, "ReadCore_NewSection", "ReadCore_KeyValue", "ReadCore_EndSection"); + SMC_SetParseStart(parser, "ReadCore_ParseStart"); + SMC_SetRawLine(parser, "ReadCore_CurrentLine"); + SMC_SetParseEnd(parser, "ReadCore_ParseEnd"); + + new line, col; + new SMCError:err = SMC_ParseFile_VDF(parser, configFile, line, col); + + if (err != SMCError_Okay) + { + new buffer[64]; + server_print("%s", SMC_GetErrorString(err, buffer, charsmax(buffer)) ? buffer : "Fatal parse error"); + } + + if (line == expectedLineCount + 1 && col == 2) + { + ++SuccessCount; + } + + SMC_DestroyParser(parser); + + server_print("^tTests successful: %d/%d", SuccessCount, expectedStartEndCount + expectedSectionCount + expectedKeyValueCount + expectedLineCount + 1); +} + +public ReadCore_ParseStart(TextParser:smc) +{ + ++SuccessCount; +} + +public ReadCore_NewSection(TextParser:smc, const name[]) +{ + ++SuccessCount; +} + +public ReadCore_KeyValue(TextParser:smc, const key[], const value[]) +{ + new buffer[128]; + if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) + { + ++SuccessCount; + } +} + +public ReadCore_EndSection(TextParser:smc) +{ + ++SuccessCount; +} + +public ReadCore_CurrentLine(TextParser:smc, const line[], lineno) +{ + ++SuccessCount; +} + +public ReadCore_ParseEnd(TextParser:smc) +{ + ++SuccessCount; +} + + + +/** + * INI Config format + */ +public ServerCommand_TextParseINI() +{ + +} diff --git a/public/ITextParsers.h b/public/ITextParsers.h new file mode 100644 index 00000000..9dff6692 --- /dev/null +++ b/public/ITextParsers.h @@ -0,0 +1,450 @@ +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * 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 General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_INTERFACE_H_ +#define _INCLUDE_SOURCEMOD_TEXTPARSERS_INTERFACE_H_ + +/** + * @file ITextParsers.h + * @brief Defines various text/file parsing functions, as well as UTF-8 support code. + */ +namespace SourceMod +{ + + #define SMINTERFACE_TEXTPARSERS_NAME "ITextParsers" + #define SMINTERFACE_TEXTPARSERS_VERSION 4 + + /** + * The INI file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: A-Z a-z 0-9 _ - , + . $ ? / + * STRING: Any set of symbols + * + * Basic syntax is comprised of SECTIONs. + * A SECTION is defined as: + * [SECTIONNAME] + * OPTION + * OPTION + * OPTION... + * + * SECTIONNAME is an IDENTIFIER. + * OPTION can be repeated any number of times, once per line. + * OPTION is defined as one of: + * KEY = "VALUE" + * KEY = VALUE + * KEY + * Where KEY is an IDENTIFIER and VALUE is a STRING. + * + * WHITESPACE should always be omitted. + * COMMENTS should be stripped, and are defined as text occurring in: + * ; + * + * Example file below. Note that + * The second line is technically invalid. The event handler + * must decide whether this should be allowed. + * --FILE BELOW-- + * [gaben] + * hi = clams + * bye = "NO CLAMS" + * + * [valve] + * cannot + * maintain + * products + */ + + /** + * @brief Contains parse events for INI files. + */ + class ITextListener_INI + { + public: + /** + * @brief Returns version number. + */ + virtual unsigned int GetTextParserVersion1() + { + return SMINTERFACE_TEXTPARSERS_VERSION; + } + public: + /** + * @brief Called when a new section is encountered in an INI file. + * + * @param section Name of section in between the [ and ] characters. + * @param invalid_tokens True if invalid tokens were detected in the name. + * @param close_bracket True if a closing bracket was detected, false otherwise. + * @param extra_tokens True if extra tokens were detected on the line. + * @param curtok Contains current token in the line where the section name starts. + * You can add to this offset when failing to point to a token. + * @return True to keep parsing, false otherwise. + */ + virtual bool ReadINI_NewSection(const char *section, + bool invalid_tokens, + bool close_bracket, + bool extra_tokens, + unsigned int *curtok) + { + return true; + } + + /** + * @brief Called when encountering a key/value pair in an INI file. + * + * @param key Name of key. + * @param value String containing value (with quotes stripped, if any). + * @param invalid_tokens Whether or not the key contained invalid tokens. + * @param equal_token There was an '=' sign present (in case the value is missing). + * @param quotes Whether value was enclosed in quotes. + * @param curtok Contains the token index of the start of the value string. + * This can be changed when returning false. + * @return True to keep parsing, false otherwise. + */ + virtual bool ReadINI_KeyValue(const char *key, + const char *value, + bool invalid_tokens, + bool equal_token, + bool quotes, + unsigned int *curtok) + { + return true; + } + + /** + * @brief Called after a line has been preprocessed, if it has text. + * + * @param line Contents of line. + * @param curtok Pointer to optionally store failed position in string. + * @return True to keep parsing, false otherwise. + */ + virtual bool ReadINI_RawLine(const char *line, unsigned int *curtok) + { + return true; + } + }; + + /** + * :TODO: write this in CFG (context free grammar) format so it makes sense + * + * The SMC file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: Any ASCII character EXCLUDING ", {, }, ;, //, / *, or WHITESPACE. + * STRING: Any set of symbols enclosed in quotes. + * Note: if a STRING does not have quotes, it is parsed as an IDENTIFIER. + * + * Basic syntax is comprised of SECTIONBLOCKs. + * A SECTIONBLOCK defined as: + * + * SECTIONNAME + * { + * OPTION + * } + * + * OPTION can be repeated any number of times inside a SECTIONBLOCK. + * A new line will terminate an OPTION, but there can be more than one OPTION per line. + * OPTION is defined any of: + * "KEY" "VALUE" + * SECTIONBLOCK + * + * SECTIONNAME, KEY, VALUE, and SINGLEKEY are strings + * SECTIONNAME cannot have trailing characters if quoted, but the quotes can be optionally removed. + * If SECTIONNAME is not enclosed in quotes, the entire sectionname string is used (minus surrounding whitespace). + * If KEY is not enclosed in quotes, the key is terminated at first whitespace. + * If VALUE is not properly enclosed in quotes, the entire value string is used (minus surrounding whitespace). + * The VALUE may have inner quotes, but the key string may not. + * + * For an example, see configs/permissions.cfg + * + * WHITESPACE should be ignored. + * Comments are text occurring inside the following tokens, and should be stripped + * unless they are inside literal strings: + * ; + * // + * / * */ + + /** + * @brief Lists actions to take when an SMC parse hook is done. + */ + enum SMCResult + { + SMCResult_Continue, /**< Continue parsing */ + SMCResult_Halt, /**< Stop parsing here */ + SMCResult_HaltFail /**< Stop parsing and return SMCError_Custom */ + }; + + /** + * @brief Lists error codes possible from parsing an SMC file. + */ + enum SMCError + { + SMCError_Okay = 0, /**< No error */ + SMCError_StreamOpen, /**< Stream failed to open */ + SMCError_StreamError, /**< The stream died... somehow */ + SMCError_Custom, /**< A custom handler threw an error */ + SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /**< A section was declared without any header */ + SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /**< A section ending has no matching beginning */ + SMCError_InvalidSection5, /**< A section beginning has no matching ending */ + SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /**< The token buffer overflowed */ + SMCError_InvalidProperty1, /**< A property was declared outside of any section */ + }; + + /** + * @brief States for line/column + */ + struct SMCStates + { + unsigned int line; /**< Current line */ + unsigned int col; /**< Current col */ + }; + + /** + * @brief Describes the events available for reading an SMC stream. + */ + class ITextListener_SMC + { + public: + /** + * @brief Returns version number. + */ + virtual unsigned int GetTextParserVersion2() + { + return SMINTERFACE_TEXTPARSERS_VERSION; + } + public: + /** + * @brief Called when starting parsing. + */ + virtual void ReadSMC_ParseStart() + { + }; + + /** + * @brief Called when ending parsing. + * + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + */ + virtual void ReadSMC_ParseEnd(bool halted, bool failed) + { + } + + /** + * @brief Called when entering a new section + * + * @param states Parsing states. + * @param name Name of section, with the colon omitted. + * @return SMCResult directive. + */ + virtual SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name) + { + return SMCResult_Continue; + } + + /** + * @brief Called when encountering a key/value pair in a section. + * + * @param states Parsing states. + * @param key Key string. + * @param value Value string. If no quotes were specified, this will be NULL, + * and key will contain the entire string. + * @return SMCResult directive. + */ + virtual SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) + { + return SMCResult_Continue; + } + + /** + * @brief Called when leaving the current section. + * + * @param states Parsing states. + * @return SMCResult directive. + */ + virtual SMCResult ReadSMC_LeavingSection(const SMCStates *states) + { + return SMCResult_Continue; + } + + /** + * @brief Called after an input line has been preprocessed. + * + * @param states Parsing states. + * @param line Contents of the line, null terminated at the position + * of the newline character (thus, no newline will exist). + * @return SMCResult directive. + */ + virtual SMCResult ReadSMC_RawLine(const SMCStates *states, const char *line) + { + return SMCResult_Continue; + } + }; + + /** + * @brief Contains various text stream parsing functions. + */ + class ITextParsers /*: public SMInterface*/ + { + public: + virtual const char *GetInterfaceName() + { + return SMINTERFACE_TEXTPARSERS_NAME; + } + virtual unsigned int GetInterfaceVersion() + { + return SMINTERFACE_TEXTPARSERS_VERSION; + } + virtual bool IsVersionCompatible(unsigned int version) + { + if (version < 2) + { + return false; + } + + return true; + /*return SMInterface::IsVersionCompatible(version);*/ + } + public: + /** + * @brief Parses an INI-format file. + * + * @param file Path to file. + * @param ini_listener Event handler for reading file. + * @param line If non-NULL, will contain last line parsed (0 if file could not be opened). + * @param col If non-NULL, will contain last column parsed (undefined if file could not be opened). + * @return True if parsing succeeded, false if file couldn't be opened or there was a syntax error. + */ + virtual bool ParseFile_INI(const char *file, + ITextListener_INI *ini_listener, + unsigned int *line, + unsigned int *col) =0; + + /** + * @brief Parses an SMC-format text file. + * Note that the parser makes every effort to obey broken syntax. + * For example, if an open brace is missing, but the section name has a colon, + * it will let you know. It is up to the event handlers to decide whether to be strict or not. + * + * @param file Path to file. + * @param smc_listener Event handler for reading file. + * @param states Optional pointer to store last known states. + * @return An SMCError result code. + */ + virtual SMCError ParseFile_SMC(const char *file, + ITextListener_SMC *smc_listener, + SMCStates *states) =0; + + /** + * @brief Converts an SMCError to a string. + * + * @param err SMCError. + * @return String error message, or NULL if none. + */ + virtual const char *GetSMCErrorString(SMCError err) =0; + + public: + /** + * @brief Returns the number of bytes that a multi-byte character contains in a UTF-8 stream. + * If the current character is not multi-byte, the function returns 1. + * + * @param stream Pointer to multi-byte ANSI character string. + * @return Number of bytes in current character. + */ + virtual unsigned int GetUTF8CharBytes(const char *stream) =0; + + /** + * @brief Returns whether the first multi-byte character in the given stream + * is a whitespace character. + * + * @param stream Pointer to multi-byte character string. + * @return True if first character is whitespace, false otherwise. + */ + virtual bool IsWhitespace(const char *stream) =0; + + /** + * @brief Same as ParseFile_SMC, but with an extended error buffer. + * + * @param file Path to file. + * @param smc_listener Event handler for reading file. + * @param states Optional pointer to store last known states. + * @param buffer Error message buffer. + * @param maxsize Maximum size of the error buffer. + * @return Error code. + */ + virtual SMCError ParseSMCFile(const char *file, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize) =0; + + /** + * @brief Parses a raw UTF8 stream as an SMC file. + * + * @param stream Memory containing data. + * @param length Number of bytes in the stream. + * @param smc_listener Event handler for reading file. + * @param states Optional pointer to store last known states. + * @param buffer Error message buffer. + * @param maxsize Maximum size of the error buffer. + * @return Error code. + */ + virtual SMCError ParseSMCStream(const char *stream, + size_t length, + ITextListener_SMC *smc_listener, + SMCStates *states, + char *buffer, + size_t maxsize) =0; + }; + + inline unsigned int _GetUTF8CharBytes(const char *stream) + { + unsigned char c = *(unsigned char *)stream; + if (c & (1<<7)) + { + if (c & (1<<5)) + { + if (c & (1<<4)) + { + return 4; + } + return 3; + } + return 2; + } + return 1; + } +} + +extern SourceMod::ITextParsers *textparsers; + +#endif //_INCLUDE_SOURCEMOD_TEXTPARSERS_INTERFACE_H_ + From 0cf5a2e12f2d0620c4a512c7591d0b415cf79eb9 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Wed, 23 Jul 2014 11:56:35 +0200 Subject: [PATCH 02/14] Expose ParseFile_INI to API. Some minor changes have been made in parser: - Added ReadINI_ParseStart and ReadINI_ParseEnd call. - Function returns SMCResult instead of bool. To avoid unecessary complexity and duplicating natives, this feels more appropriate to let these functions to share some SMCResult and SMCParse constants. Since properly documented, this should be ok. - Made sure curtok is set to 0 when ptr_val is null, otherwise unexpected value would be passed into the forward. --- amxmodx/CTextParsers.cpp | 36 +- amxmodx/CTextParsers.h | 2 +- amxmodx/msvc10/amxmodx_mm.vcxproj | 1 + amxmodx/msvc10/amxmodx_mm.vcxproj.filters | 14 +- amxmodx/textparse.cpp | 87 +++-- amxmodx/textparse.h | 57 ++- plugins/include/textparse.inc | 411 +++++++++++++--------- plugins/testsuite/textparse_test.cfg | 135 +++++++ plugins/testsuite/textparse_test.ini | 68 ++++ plugins/testsuite/textparse_test.sma | 140 +++++++- public/ITextParsers.h | 107 +++--- support/PackageScript | 3 + 12 files changed, 776 insertions(+), 285 deletions(-) create mode 100644 plugins/testsuite/textparse_test.cfg create mode 100644 plugins/testsuite/textparse_test.ini diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp index 4a3f63b1..5ebcfaed 100644 --- a/amxmodx/CTextParsers.cpp +++ b/amxmodx/CTextParsers.cpp @@ -778,7 +778,7 @@ failed: * INI parser */ -bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) +SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) { FILE *fp = fopen(file, "rt"); unsigned int curline = 0; @@ -792,12 +792,16 @@ bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listene *line = 0; } - return false; + return SMCError_StreamOpen; } + ini_listener->ReadINI_ParseStart(); + char buffer[2048]; char *ptr, *save_ptr; bool in_quote; + SMCError err = SMCError_Okay; + SMCResult res = SMCResult_Continue; while (!feof(fp)) { @@ -897,8 +901,9 @@ bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listene continue; } - if (!ini_listener->ReadINI_RawLine(ptr, &curtok)) + if ((res = ini_listener->ReadINI_RawLine(ptr, curline, &curtok)) != SMCResult_Continue) { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; goto event_failed; } @@ -951,8 +956,9 @@ bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listene } /* Tell the handler */ - if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok)) + if ((res = ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok)) != SMCResult_Continue) { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; goto event_failed; } } @@ -1052,9 +1058,14 @@ bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listene } skip_value: /* We're done! */ - curtok = val_ptr - buffer; - if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok)) + if (val_ptr) + curtok = val_ptr - buffer; + else + curtok = 0; + + if ((res = ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok)) != SMCResult_Continue) { + err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; curtok = 0; goto event_failed; } @@ -1066,9 +1077,16 @@ bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listene *line = curline; } + if (col) + { + *col = curtok; + } + fclose(fp); - return true; + ini_listener->ReadINI_ParseEnd(false, false); + + return SMCError_Okay; event_failed: if (line) @@ -1083,7 +1101,9 @@ event_failed: fclose(fp); - return false; + ini_listener->ReadINI_ParseEnd(true, (err == SMCError_Custom)); + + return err; } const char *TextParsers::GetSMCErrorString(SMCError err) diff --git a/amxmodx/CTextParsers.h b/amxmodx/CTextParsers.h index 45ebe52c..7aaafb63 100644 --- a/amxmodx/CTextParsers.h +++ b/amxmodx/CTextParsers.h @@ -51,7 +51,7 @@ class TextParsers : public ITextParsers public: TextParsers(); public: - bool ParseFile_INI(const char *file, + SMCError ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col); diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index 069d2c7a..1d8eec87 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -392,6 +392,7 @@ + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index 67096bb7..bff2487d 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -323,18 +323,12 @@ Header Files -<<<<<<< HEAD -======= - - Header Files - - - Header Files - Header Files - ->>>>>>> Introduce TextParser API. + + + Header Files + diff --git a/amxmodx/textparse.cpp b/amxmodx/textparse.cpp index 0ed02579..b34bdc61 100644 --- a/amxmodx/textparse.cpp +++ b/amxmodx/textparse.cpp @@ -37,7 +37,12 @@ TextParserHandles g_TextParsersHandles; static cell AMX_NATIVE_CALL SMC_CreateParser(AMX *amx, cell *params) { - return static_cast(g_TextParsersHandles.create()); + int handle = g_TextParsersHandles.create(); + ParseInfo *p = g_TextParsersHandles.lookup(handle); + + p->ini_format = params[1] > 0 ? true : false; + + return static_cast(handle); } static cell AMX_NATIVE_CALL SMC_SetParseStart(AMX *amx, cell *params) @@ -77,7 +82,7 @@ static cell AMX_NATIVE_CALL SMC_SetParseEnd(AMX *amx, cell *params) int length; const char* funcName = get_amxstring(amx, params[2], 0, length); - int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_DONE); + int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_CELL, FP_CELL, FP_DONE); if (func == -1) { @@ -101,34 +106,50 @@ static cell AMX_NATIVE_CALL SMC_SetReaders(AMX *amx, cell *params) } int length; - const char* NewSectionFuncName = get_amxstring(amx, params[2], 0, length); - const char* KeyValueFuncName = get_amxstring(amx, params[3], 1, length); - const char* EndSectionFuncName = get_amxstring(amx, params[4], 2, length); + const char* newSectionFuncName = get_amxstring(amx, params[2], 0, length); + const char* keyValueFuncName = get_amxstring(amx, params[3], 1, length); + const char* endSectionFuncName = get_amxstring(amx, params[4], 2, length); - int NewSectionFunc = registerSPForwardByName(amx, NewSectionFuncName, FP_CELL, FP_STRING, FP_DONE); - if (NewSectionFunc == -1) + int newSectionFunc; + int keyValueFunc; + int endSectionFunc; + + if (p->ini_format) + newSectionFunc = registerSPForwardByName(amx, newSectionFuncName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + else + newSectionFunc = registerSPForwardByName(amx, newSectionFuncName, FP_CELL, FP_STRING, FP_DONE); + + if (newSectionFunc == -1) { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", NewSectionFuncName, g_plugins.findPluginFast(amx)->getName()); + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", newSectionFuncName, g_plugins.findPluginFast(amx)->getName()); return 0; } - int KeyValueFunc = registerSPForwardByName(amx, KeyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_DONE); - if (KeyValueFunc == -1) + if (p->ini_format) + keyValueFunc = registerSPForwardByName(amx, keyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + else + keyValueFunc = registerSPForwardByName(amx, keyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_DONE); + + if (keyValueFunc == -1) { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", KeyValueFuncName, g_plugins.findPluginFast(amx)->getName()); + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", keyValueFuncName, g_plugins.findPluginFast(amx)->getName()); return 0; } - int EndSectionFunc = registerSPForwardByName(amx, EndSectionFuncName, FP_CELL, FP_DONE); - if (EndSectionFunc == -1) + if (!p->ini_format) { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", EndSectionFuncName, g_plugins.findPluginFast(amx)->getName()); - return 0; + endSectionFunc = registerSPForwardByName(amx, endSectionFuncName, FP_CELL, FP_DONE); + if (endSectionFunc == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", endSectionFuncName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + p->end_section = endSectionFunc; } - p->new_section = NewSectionFunc; - p->key_value = KeyValueFunc; - p->end_section = EndSectionFunc; + p->new_section = newSectionFunc; + p->key_value = keyValueFunc; return 1; } @@ -146,7 +167,12 @@ static cell AMX_NATIVE_CALL SMC_SetRawLine(AMX *amx, cell *params) int length; const char* funcName = get_amxstring(amx, params[2], 0, length); - int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_DONE); + int func; + if (p->ini_format) + func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_DONE); + else + func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_DONE); + if (func == -1) { LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); @@ -171,13 +197,26 @@ static cell AMX_NATIVE_CALL SMC_ParseFile(AMX *amx, cell *params) int length; const char *file = build_pathname("%s", get_amxstring(amx, params[2], 0, length)); - SMCStates states; - SMCError p_err = textparsers->ParseFile_SMC(file, p, &states); + SMCError p_err; - *get_amxaddr(amx, params[3]) = states.line; - *get_amxaddr(amx, params[4]) = states.col; + if (p->ini_format) + { + size_t line, col; + p_err = textparsers->ParseFile_INI(file, p, &line, &col); - return (cell)p_err; + *get_amxaddr(amx, params[3]) = line; + *get_amxaddr(amx, params[4]) = col; + } + else + { + SMCStates states; + p_err = textparsers->ParseFile_SMC(file, p, &states); + + *get_amxaddr(amx, params[3]) = states.line; + *get_amxaddr(amx, params[4]) = states.col; + } + + return static_cast(p_err); } static cell AMX_NATIVE_CALL SMC_GetErrorString(AMX *amx, cell *params) diff --git a/amxmodx/textparse.h b/amxmodx/textparse.h index cdb3ec02..10b55dd3 100644 --- a/amxmodx/textparse.h +++ b/amxmodx/textparse.h @@ -35,7 +35,9 @@ #include "amxmodx.h" #include "CTextParsers.h" -class ParseInfo : public ITextListener_SMC +class ParseInfo : + public ITextListener_SMC, + public ITextListener_INI { public: ParseInfo() @@ -47,31 +49,43 @@ public: end_section = -1; raw_line = -1; handle = -1; + ini_format = false; } - ~ParseInfo() {} + public: void ReadSMC_ParseStart() { - if (parse_start != -1) - { + if (parse_start != -1) + executeForwards(parse_start, handle); + } + void ReadINI_ParseStart() + { + if (parse_start != -1) executeForwards(parse_start, handle); - } } void ReadSMC_ParseEnd(bool halted, bool failed) { - if (parse_end != -1) - { + if (parse_end != -1) + executeForwards(parse_end, handle, halted ? 1 : 0, failed ? 1 : 0); + } + void ReadINI_ParseEnd(bool halted, bool failed) + { + if (parse_end != -1) executeForwards(parse_end, handle, halted ? 1 : 0, failed ? 1 : 0); - } } SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name) { if (new_section != -1) - { return (SMCResult)executeForwards(new_section, handle, name); - } + + return SMCResult_Continue; + } + SMCResult ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) + { + if (new_section != -1) + return (SMCResult)executeForwards(new_section, handle, section, invalid_tokens, close_bracket, extra_tokens, *curtok); return SMCResult_Continue; } @@ -79,9 +93,14 @@ public: SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) { if (key_value != -1) - { return (SMCResult)executeForwards(key_value, handle, key, value); - } + + return SMCResult_Continue; + } + SMCResult ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) + { + if (key_value != -1) + return (SMCResult)executeForwards(key_value, handle, key, value, invalid_tokens, equal_token, quotes, *curtok); return SMCResult_Continue; } @@ -89,9 +108,7 @@ public: SMCResult ReadSMC_LeavingSection(const SMCStates *states) { if (end_section != -1) - { - return (SMCResult)executeForwards(end_section, handle, handle); - } + return (SMCResult)executeForwards(end_section, handle); return SMCResult_Continue; } @@ -99,9 +116,14 @@ public: SMCResult ReadSMC_RawLine(const SMCStates *states, const char *line) { if (raw_line != -1) - { return (SMCResult)executeForwards(raw_line, handle, line, states->line); - } + + return SMCResult_Continue; + } + SMCResult ReadINI_RawLine(const char *line, unsigned int lineno, unsigned int *curtok) + { + if (raw_line != -1) + return (SMCResult)executeForwards(raw_line, handle, line, lineno, *curtok); return SMCResult_Continue; } @@ -113,6 +135,7 @@ public: int end_section; int raw_line; int handle; + bool ini_format; }; template diff --git a/plugins/include/textparse.inc b/plugins/include/textparse.inc index 2ae996c5..d7eef5ef 100644 --- a/plugins/include/textparse.inc +++ b/plugins/include/textparse.inc @@ -1,46 +1,98 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This file is part of the SourceMod/SourcePawn SDK. - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * 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 General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - + #if defined _textparse_included #endinput #endif #define _textparse_included +/** + * This parser API is entirely event based. You must hook events to receive data. + * The file format can ben either INI or SMC (also known as "SourceMod Configuration". + * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). + * Also please note INI format is handled differently. Because more "simple" to parse, some + * event doesn't exist and callback prototype can be different. + */ /** - * Everything below describes the SMC Parse, or "SourceMod Configuration" format. - * This parser is entirely event based. You must hook events to receive data. - * The file format itself is nearly identical to Valve's KeyValues format. + * The INI file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: A-Z a-z 0-9 _ - , + . $ ? / + * STRING : Any set of symbols + * + * Basic syntax is comprised of SECTIONs. + * A SECTION is defined as: + * [SECTIONNAME] + * OPTION + * OPTION + * OPTION... + * + * SECTIONNAME is an IDENTIFIER. + * OPTION can be repeated any number of times, once per line. + * OPTION is defined as one of: + * KEY = "VALUE" + * KEY = VALUE + * KEY + * Where KEY is an IDENTIFIER and VALUE is a STRING. + * + * WHITESPACE should always be omitted. + * COMMENTS should be stripped, and are defined as text occurring in: + * ; + * + * Example file below. Note that the second line is technically invalid. + * The event handler must decide whether this should be allowed. + * --FILE BELOW-- + * [gaben] + * hi = clams + * bye = "NO CLAMS" + * + * [valve] + * cannot + * maintain + * products */ + +/** + * The SMC file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: Any ASCII character EXCLUDING ", {, }, ;, //, / *, or WHITESPACE. + * STRING : Any set of symbols enclosed in quotes. + * + * Note: if a STRING does not have quotes, it is parsed as an IDENTIFIER. + * + * Basic syntax is comprised of SECTIONBLOCKs. + * A SECTIONBLOCK defined as: + * + * SECTIONNAME + * { + * OPTION + * } + * + * OPTION can be repeated any number of times inside a SECTIONBLOCK. + * A new line will terminate an OPTION, but there can be more than one OPTION per line. + * OPTION is defined any of: + * "KEY" "VALUE" + * SECTIONBLOCK + * + * SECTIONNAME, KEY, VALUE, and SINGLEKEY are strings + * SECTIONNAME cannot have trailing characters if quoted, but the quotes can be optionally removed. + * If SECTIONNAME is not enclosed in quotes, the entire sectionname string is used (minus surrounding whitespace). + * If KEY is not enclosed in quotes, the key is terminated at first whitespace. + * If VALUE is not properly enclosed in quotes, the entire value string is used (minus surrounding whitespace). + * The VALUE may have inner quotes, but the key string may not. + * + * For an example, see scripting/testsuite/textparse_test.cfg + * + * WHITESPACE should be ignored. + * Comments are text occurring inside the following tokens, and should be stripped + * unless they are inside literal strings: + * ; + * // + * / * * / + */ + +enum TextParser +{ + Invalid_TextParser = 0 +}; /** * Parse result directive. @@ -71,31 +123,183 @@ enum SMCError SMCError_InvalidProperty1, /**< A property was declared outside of any section */ }; -enum TextParser -{ - Invalid_TextPaser = 0 -}; /** - * Creates a new SMC file format parser. + * Creates a new text parser. * This is used to set parse hooks. * - * @return A new Handle to an SMC Parse structure. + * @param ini_format Sets whether INI format or SMC should be used. + * + * @return A new handle to an Text Parse structure. */ -native TextParser:SMC_CreateParser(); +native TextParser:SMC_CreateParser(bool:ini_format = false); /** - * Parses an SMC file. + * Disposes of a text parser. * - * @param smc A Handle to an SMC Parse structure. + * @param handle Handle to a Text Parse. + * @return True if disposed, false otherwise. + */ +native SMC_DestroyParser(&TextParser:handle); + +/** + * Parses a config file. + * + * @note If using INI format, you can expect only the following SMCError_* constants: + * SMCError_Okay, SMCError_StreamOpen and SMCError_Custom. + * + * @param handle A handle to an Text Parse structure. * @param file A string containing the file path. * @param line An optional by reference cell to store the last line number read. * @param col An optional by reference cell to store the last column number read. - * @return An SMCParseError result. - * @error Invalid or corrupt Handle. + * @return A SMCParseError result. + * @error Invalid or corrupt handle. */ -native SMCError:SMC_ParseFile(TextParser:smc, const file[], &line = 0, &col = 0); +native SMCError:SMC_ParseFile(TextParser:handle, const file[], &line = 0, &col = 0); + +/** + * Sets the SMC_ParseStart function of a parse handle. + * Below is the prototype of callback.: + * + * - ParseStart: + * Called when parsing is started. + * + * @param handle The Text Parse handle. + * @noreturn + * + * public SMC_ParseStart(TextParser:handle) + * + * @param handle Handle to a Text Parse. + * @param func A ParseStart callback. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native SMC_SetParseStart(TextParser:handle, const func[]); + +/** + * Sets the SMC_ParseEnd of a parse handle. + * Below is the prototype of callback.: + * + * - ParseEnd: + * Called when parsing is halted. + * + * @param handle The Text Parse handle. + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + * If using INI format, this is only true when + * a callback returns SMCResult_HaltFail. + * @noreturn + * + * public SMC_ParseEnd(TextParser:handle, bool:halted, bool:failed) + * + * @param handle Handle to a Text Parse. + * @param func A ParseEnd callback.. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native SMC_SetParseEnd(TextParser:handle, const func[]); + +/** + * Sets the three main reader functions. + * Below are the different prototypes of callbacks: + * + * - NewSection: + * Called when the parser finds the end of the current section. + * + * INI: + * @param section Name of section in between the [ and ] characters. + * @param invalid_tokens True if invalid tokens were detected in the name. + * @param close_bracket True if a closing bracket was detected, false otherwise. + * @param extra_tokens True if extra tokens were detected on the line. + * @param curtok Contains current token in the line where the section name starts. + * can add to this offset when failing to point to a token. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_NewSection(TextParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) + * + * SMC: + * @note Enclosing quotes are always stripped. + * @param handle The Text Parse handle. + * @param name String containing section name. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_NewSection(TextParser:handle, const name[]) + * + * - KeyValue: + * Called when the parser finds a new key/value pair. + * + * INI: + * @param handle The Text Parse handle. + * @param key Name of key. + * @param value String containing value (with quotes stripped, if any). + * @param invalid_tokens Whether or not the key contained invalid tokens. + * @param equal_token There was an '=' sign present (in case the value is missing). + * @param quotes Whether value was enclosed in quotes. + * @param curtok Contains the token index of the start of the value string. + * This can be changed when returning false. + * @return SMCResult directive. + * + * public SMCResult:SMC_KeyValue(TextParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) + * + * SMC: + * @note Enclosing quotes are always stripped. + * + * @param handle The Text Parse handle. + * @param key String containing key name. + * @param value String containing value name. + * @return A SMCResult action to take. + * + * public SMCResult:SMC_KeyValue(TextParser:handle, const key[], const value[]) + * + * - EndSection: + * Called when the parser finds the end of the current section. + * Only with SMC format. + + * @param handle The Text Parse handle. + * + * public SMCResult:SMC_EndSection(TextParser:handle) + * - + * @param handle The Text parse handle. + * @param ns A NewSection callback. + * @param kv A KeyValue callback. + * @param es An EndSection callback. Only available for SMC config format. + * + * @noreturn + */ +native SMC_SetReaders(TextParser:smc, const nsFunc[], const kvFunc[], const esFunc[] = ""); + +/** + * Sets a raw line reader on an text parser handle. + * Below is the prototype of callback.: + * + * - RawLine: + * Called whenever a raw line is read. + * + * INI: + * @param handle The Text Parse handle. + * @param line Contents of line. + * @param lineno The line number it occurs on. + * @param curtok Pointer to optionally store failed position in string. + * @return SMCResult directive. + * + * public SMCResult:SMC_RawLine(TextParser:smc, const line[], lineno, curtok) + * + * SMC: + * @param handle The Text Parse handle. + * @param line A string containing the raw line from the file. + * @param lineno The line number it occurs on. + * @return An SMCResult action to take. + * + * public SMCResult:SMC_RawLine(TextParser:handle, const line[], lineno) + * + * @param handle Handle to an Text Parse. + * @param func A RawLine callback. + * @noreturn + */ +native SMC_SetRawLine(TextParser:handle, const func[]); /** * Gets an error string for an SMCError code. @@ -109,115 +313,4 @@ native SMCError:SMC_ParseFile(TextParser:smc, const file[], &line = 0, &col = 0) * * @return True on success, false otherwise. */ -native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); - -/** - * Sets the SMC_ParseStart function of a parse Handle. - * Below is the prototype of callback.: - * - * - ParseStart: - * Called when parsing is started. - * - * @param smc The SMC Parse Handle. - * @noreturn - * - * public SMC_ParseStart(TextParser:smc) - * - * @param smc Handle to an SMC Parse. - * @param func A ParseStart callback. - * - * @noreturn - * @error Invalid or corrupt Handle. - */ -native SMC_SetParseStart(TextParser:smc, const func[]); - -/** - * Sets the SMC_ParseEnd of a parse handle. - * Below is the prototype of callback.: - * - * - ParseEnd: - * Called when parsing is halted. - * - * @param smc The SMC Parse Handle. - * @param halted True if abnormally halted, false otherwise. - * @param failed True if parsing failed, false otherwise. - * @noreturn - * - * public SMC_ParseEnd(TextParser:smc, bool:halted, bool:failed) - * - * @param smc Handle to an SMC Parse. - * @param func A ParseEnd callback.. - * - * @noreturn - * @error Invalid or corrupt Handle. - */ -native SMC_SetParseEnd(TextParser:smc, const func[]); - -/** - * Sets the three main reader functions. - * Below are the different prototypes of callbacks: - * - * - NewSection: - * Called when the parser finds the end of the current section. - * @note Enclosing quotes are always stripped. - * - * @param smc The SMC Parse Handle. - * @param name String containing section name. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_NewSection(TextParser:smc, const name[]) - * - * - KeyValue: - * Called when the parser finds a new key/value pair. - * @note Enclosing quotes are always stripped. - * - * @param smc The SMC Parse Handle. - * @param key String containing key name. - * @param value String containing value name. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_KeyValue(TextParser:smc, const key[], const value[]) - * - * - EndSection: - * Called when the parser finds the end of the current section. - * - * @param smc The SMC Parse Handle. - * - * public SMCResult:SMC_EndSection(TextParser:smc) - * - - * @param smc An SMC parse Handle. - * @param ns A NewSection callback. - * @param kv A KeyValue callback. - * @param es An EndSection callback. - * - * @noreturn - */ -native SMC_SetReaders(TextParser:smc, const nsFunc[], const kvFunc[], const esFunc[]); - -/** - * Sets a raw line reader on an SMC parser Handle. - * Below is the prototype of callback.: - * - * - RawLine: - * Called whenever a raw line is read. - * - * @param smc The SMC Parse Handle. - * @param line A string containing the raw line from the file. - * @param lineno The line number it occurs on. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_RawLine(TextParser:smc, const line[], lineno) - * - * @param smc Handle to an SMC Parse. - * @param func A RawLine callback.. - * @noreturn - */ -native SMC_SetRawLine(TextParser:smc, const func[]); - -/** - * Disposes of a text parser. - * - * @param smc Handle to an SMC Parse. - * @return True if disposed, false otherwise. - */ -native SMC_DestroyParser(&TextParser:smc); \ No newline at end of file +native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); \ No newline at end of file diff --git a/plugins/testsuite/textparse_test.cfg b/plugins/testsuite/textparse_test.cfg new file mode 100644 index 00000000..5bf0cea0 --- /dev/null +++ b/plugins/testsuite/textparse_test.cfg @@ -0,0 +1,135 @@ +/** + * This file is used to set various options that are important to SourceMod's core. + * If this file is missing or an option in this file is missing, then the default values will be used. + */ +"Core" +{ + /** + * This option determines if SourceMod logging is enabled. + * + * "on" - Logging is enabled (default) + * "off" - Logging is disabled + */ + "Logging" "on" + + /** + * This option determines how SourceMod logging should be handled. + * + * "daily" - New log file is created for each day (default) + * "map" - New log file is created for each map change + * "game" - Use game's log files + */ + "LogMode" "daily" + + /** + * Language that multilingual enabled plugins and extensions will use to print messages. + * Only languages listed in languages.cfg are valid. + * + * The default value is "en" + */ + "ServerLang" "en" + + /** + * String to use as the public chat trigger. Set an empty string to disable. + */ + "PublicChatTrigger" "!" + + /** + * String to use as the silent chat trigger. Set an empty string to disable. + */ + "SilentChatTrigger" "/" + + /** + * If a say command is a silent chat trigger, and is used by an admin, + * but it does not evaluate to an actual command, it will be displayed + * publicly. This setting allows you to suppress accidental typings. + * + * The default value is "no". A value of "yes" will supress. + */ + "SilentFailSuppress" "no" + + /** + * Password setinfo key that clients must set. You must change this in order for + * passwords to work, for security reasons. + */ + "PassInfoVar" "_password" + + /** + * Specifies the sound that gets played when an item is selected from a menu. + */ + "MenuItemSound" "buttons/button14.wav" + + /** + * Specifies the sound that gets played when an "Exit" button is selected + * from a menu. + */ + "MenuExitSound" "buttons/combine_button7.wav" + + /** + * Specifies the sound that gets played when an "Exit Back" button is selected + * from a menu. This is the special "Back" button that is intended to roll back + * to a previous menu. + */ + "MenuExitBackSound" "buttons/combine_button7.wav" + + /** + * Enables or disables whether SourceMod reads a client's cl_language cvar to set + * their language for server-side phrase translation. + * + * "on" - Translate using the client's language (default) + * "off" - Translate using default server's language + */ + "AllowClLanguageVar" "On" + + /** + * Enables or Disables SourceMod's automatic gamedata updating. + * + * The default value is "no". A value of "yes" will block the Auto Updater. + */ + "DisableAutoUpdate" "no" + + /** + * If set to yes, a successful gamedata update will attempt to restart SourceMod. + * SourceMod is unloaded and reloaded, and the map is changed to the current map. + * Since gamedata updates occur when the server loads, impact should be minimal. + * But to be safe, this option is disabled by default. + */ + "ForceRestartAfterUpdate" "no" + + /** + * URL to use for retrieving update information. + * SSL is not yet supported. + */ + "AutoUpdateURL" "http://update.sourcemod.net/update/" + + /** + * Whether to show debug spew. + * Currently this will log details about the gamedata updating process. + */ + "DebugSpew" "no" + + /** + * If set to yes, SourceMod will validate steamid auth strings with the Steam backend before giving out admin access. + * This can prevent malicious users from impersonating admins with stolen Steam apptickets. + * If Steam is down, admins will not be authenticated until Steam comes back up. + * This option increases the security of your server, but is still experimental. + */ + "SteamAuthstringValidation" "yes" + + /** + * Enables or disables whether SourceMod blocks known or potentially malicious plugins from loading. + * It is STRONGLY advised that this is left enabled, there have been cases in the past with plugins that + * allow anyone to delete files on the server, gain full rcon control, etc. + * + * "yes" - Block malware or illegal plugins from loading (default) + * "no" - Warn about malware or illegal plugins loading + */ + "BlockBadPlugins" "yes" + + /** + * If a plugin takes too long to execute, hanging or freezing the game server in the process, + * SourceMod will attempt to terminate that plugin after the specified timeout length has + * passed. You can disable this feature by setting the value to "0". + */ + "SlowScriptTimeout" "8" +} \ No newline at end of file diff --git a/plugins/testsuite/textparse_test.ini b/plugins/testsuite/textparse_test.ini new file mode 100644 index 00000000..88078331 --- /dev/null +++ b/plugins/testsuite/textparse_test.ini @@ -0,0 +1,68 @@ +;CSDM Configuration File +; Default settings by BAILOPAN + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;You must be running the Main plugin for this section +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[settings] + +;Sets whether CSDM is enabled or not. +enabled = 1 + +;Sets whether or not players should be stripped of weapons on round start +; (excludes knife) +strip_weapons = 1 + +;Sets how long weapons should stay on the ground for after being dropped +;in seconds. note that enabling this can create lots of lag for clients +; AND server. 0 is immediate, -1 is infinite. +weapons_stay = 0 + +;Sets the spawn mode. +; "none" - users spawn at normal map spawn points +; "preset" - csdm_spawn_preset.amxx required, uses predefined spawns in config files +; -- others may be supplied by 3rd party plugins +spawnmode = preset + +;Sets whether the bomb is removed +remove_bomb = 1 + +;Sets the spawn waiting time +spawn_wait_time = 0.75 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;You must be running the protection plugin for this section +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +[protection] ; some comment + +;Is spawn protection enabled? +enabled = 1 + +;Colors of glow shell, leave this in quotes +;The digits are R, G, B, A where A is the alpha transparency +; (as A gets higher, the glow shell is thicker) +colors = "0 255 0 200" + +;Number of seconds someone is respawned for. +time = 2 + +;;;;;;;;;;;;;;;; +;;WEAPON MENUS;; +;;;;;;;;;;;;;;;; + +;Format for weapon menus is: +;shortname "Display Name" menupage +;Change the '1' to a '0' to block the weapon +;Removing or moving things +; from the list will change the order of the menus! + +[secondary] gabe // < just to test param. +usp USP 1 +glock18 Glock 1 +deagle Deagle 1 + +;List weapons here the bots can randomly have +;The short name must match one in the list above +[botsecondary +deagle +usp diff --git a/plugins/testsuite/textparse_test.sma b/plugins/testsuite/textparse_test.sma index d5ab402c..297aeeb7 100644 --- a/plugins/testsuite/textparse_test.sma +++ b/plugins/testsuite/textparse_test.sma @@ -6,17 +6,24 @@ new Trie:ExpectedKVData; public plugin_init() { - register_concmd("textparse_vdf", "ConsoleCommand_TextParseVDF"); - register_clcmd("textparse_ini", "ServerCommand_TextParseINI"); + register_concmd("textparse", "ConsoleCommand_TextParse"); +} + +public ConsoleCommand_TextParse() +{ + InitializeTextParseSMC(); + InitializeTextParseINI(); } /** - * VDF Config format + * SMC Config format */ -public ConsoleCommand_TextParseVDF() +InitializeTextParseSMC() { - server_print("Testing text parser with VDF config file format..."); + SuccessCount = 0; + + server_print("Testing text parser with SMC config file format..."); new const configFile[] = "addons/amxmodx/scripting/testsuite/textparse_test.cfg"; @@ -53,7 +60,7 @@ public ConsoleCommand_TextParseVDF() SMC_SetParseEnd(parser, "ReadCore_ParseEnd"); new line, col; - new SMCError:err = SMC_ParseFile_VDF(parser, configFile, line, col); + new SMCError:err = SMC_ParseFile(parser, configFile, line, col); if (err != SMCError_Okay) { @@ -66,22 +73,24 @@ public ConsoleCommand_TextParseVDF() ++SuccessCount; } - SMC_DestroyParser(parser); - server_print("^tTests successful: %d/%d", SuccessCount, expectedStartEndCount + expectedSectionCount + expectedKeyValueCount + expectedLineCount + 1); + + SMC_DestroyParser(parser); + TrieDestroy(ExpectedKVData); + SuccessCount = 0; } -public ReadCore_ParseStart(TextParser:smc) +public ReadCore_ParseStart(TextParser:handle) { ++SuccessCount; } -public ReadCore_NewSection(TextParser:smc, const name[]) +public ReadCore_NewSection(TextParser:handle, const name[]) { ++SuccessCount; } -public ReadCore_KeyValue(TextParser:smc, const key[], const value[]) +public ReadCore_KeyValue(TextParser:handle, const key[], const value[]) { new buffer[128]; if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) @@ -90,27 +99,124 @@ public ReadCore_KeyValue(TextParser:smc, const key[], const value[]) } } -public ReadCore_EndSection(TextParser:smc) +public ReadCore_EndSection(TextParser:handle) { ++SuccessCount; } -public ReadCore_CurrentLine(TextParser:smc, const line[], lineno) +public ReadCore_CurrentLine(TextParser:handle, const line[], lineno) { ++SuccessCount; } -public ReadCore_ParseEnd(TextParser:smc) +public ReadCore_ParseEnd(TextParser:handle, bool:halted, bool:failed) { ++SuccessCount; } - /** * INI Config format */ -public ServerCommand_TextParseINI() -{ +public InitializeTextParseINI() +{ + SuccessCount = 0; + + server_print("Testing text parser with INI config file format..."); + + new const configFile[] = "addons/amxmodx/scripting/testsuite/textparse_test.ini"; + + ExpectedKVData = TrieCreate(); + TrieSetString(ExpectedKVData, "settings", ""); + TrieSetString(ExpectedKVData, "enabled", "1"); + TrieSetString(ExpectedKVData, "strip_weapons", "1"); + TrieSetString(ExpectedKVData, "weapons_stay", "0"); + TrieSetString(ExpectedKVData, "spawnmode", "preset"); + TrieSetString(ExpectedKVData, "remove_bomb", "1"); + TrieSetString(ExpectedKVData, "spawn_wait_time", "0.75"); + TrieSetString(ExpectedKVData, "protection", ""); + TrieSetString(ExpectedKVData, "colors", "0 255 0 200"); + TrieSetString(ExpectedKVData, "time", "time"); + TrieSetString(ExpectedKVData, "secondary", ""); + TrieSetString(ExpectedKVData, "usp USP 1", ""); + TrieSetString(ExpectedKVData, "glock18 Glock 1", ""); + TrieSetString(ExpectedKVData, "deagle Deagle 1", ""); + TrieSetString(ExpectedKVData, "botsecondary", ""); + TrieSetString(ExpectedKVData, "deagle", ""); + TrieSetString(ExpectedKVData, "usp", ""); + + new const expectedSectionCount = 4; + new const expectedStartEndCount = 2; + new const expectedKeyValueCount = TrieGetSize(ExpectedKVData) - expectedSectionCount; + new const expectedLineCount = TrieGetSize(ExpectedKVData); // This doesn't include blanck/comments line. + + new TextParser:parser = SMC_CreateParser(.ini_format = true); + + SMC_SetReaders(parser, "ReadCSDM_NewSection", "ReadCSDM_KeyValue"); + SMC_SetParseStart(parser, "ReadCSDM_ParseStart"); + SMC_SetRawLine(parser, "ReadCSDM_CurrentLine"); + SMC_SetParseEnd(parser, "ReadCSDM_ParseEnd"); + + new line, col; + new SMCError:err = SMC_ParseFile(parser, configFile, line, col); + + if (err != SMCError_Okay) + { + new buffer[64]; + server_print("Error: %s", SMC_GetErrorString(err, buffer, charsmax(buffer)) ? buffer : "Fatal parse error"); + } + + if (line == expectedLineCount + 1) + { + ++SuccessCount; + } + + server_print("^tTests successful: %d/%d", SuccessCount, expectedStartEndCount + expectedSectionCount + expectedKeyValueCount + expectedLineCount + 1); + + SMC_DestroyParser(parser); + TrieDestroy(ExpectedKVData); } + +public ReadCSDM_ParseStart(TextParser:handle) +{ + ++SuccessCount; +} + +public ReadCSDM_NewSection(TextParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) +{ + if (TrieKeyExists(ExpectedKVData, section)) + { + if ((equal(section, "secondary") && !extra_tokens) || + (equal(section, "botsecondary") && close_bracket)) + { + return; + } + + ++SuccessCount; + } +} + +public ReadCSDM_KeyValue(TextParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) +{ + new buffer[128]; + if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) + { + if (equal(value, "colors") && !quotes) + { + return; + } + + ++SuccessCount; + } +} + +public ReadCSDM_CurrentLine(TextParser:handle, const line[], lineno, curtok) +{ + ++SuccessCount; +} + +public ReadCSDM_ParseEnd(TextParser:handle, bool:halted, bool:failed) +{ + ++SuccessCount; +} \ No newline at end of file diff --git a/public/ITextParsers.h b/public/ITextParsers.h index 9dff6692..a8ef31b4 100644 --- a/public/ITextParsers.h +++ b/public/ITextParsers.h @@ -81,6 +81,35 @@ namespace SourceMod * products */ + /** + * @brief Lists actions to take when an SMC parse hook is done. + */ + enum SMCResult + { + SMCResult_Continue, /**< Continue parsing */ + SMCResult_Halt, /**< Stop parsing here */ + SMCResult_HaltFail /**< Stop parsing and return SMCError_Custom */ + }; + + /** + * @brief Lists error codes possible from parsing an SMC file. + */ + enum SMCError + { + SMCError_Okay = 0, /**< No error */ + SMCError_StreamOpen, /**< Stream failed to open */ + SMCError_StreamError, /**< The stream died... somehow */ + SMCError_Custom, /**< A custom handler threw an error */ + SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /**< A section was declared without any header */ + SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /**< A section ending has no matching beginning */ + SMCError_InvalidSection5, /**< A section beginning has no matching ending */ + SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /**< The token buffer overflowed */ + SMCError_InvalidProperty1, /**< A property was declared outside of any section */ + }; + /** * @brief Contains parse events for INI files. */ @@ -95,6 +124,23 @@ namespace SourceMod return SMINTERFACE_TEXTPARSERS_VERSION; } public: + /** + * @brief Called when starting parsing. + */ + virtual void ReadINI_ParseStart() + { + }; + + /** + * @brief Called when ending parsing. + * + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + */ + virtual void ReadINI_ParseEnd(bool halted, bool failed) + { + } + /** * @brief Called when a new section is encountered in an INI file. * @@ -104,15 +150,11 @@ namespace SourceMod * @param extra_tokens True if extra tokens were detected on the line. * @param curtok Contains current token in the line where the section name starts. * You can add to this offset when failing to point to a token. - * @return True to keep parsing, false otherwise. + * @return SMCResult directive. */ - virtual bool ReadINI_NewSection(const char *section, - bool invalid_tokens, - bool close_bracket, - bool extra_tokens, - unsigned int *curtok) + virtual SMCResult ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) { - return true; + return SMCResult_Continue; } /** @@ -125,28 +167,24 @@ namespace SourceMod * @param quotes Whether value was enclosed in quotes. * @param curtok Contains the token index of the start of the value string. * This can be changed when returning false. - * @return True to keep parsing, false otherwise. + * @return SMCResult directive. */ - virtual bool ReadINI_KeyValue(const char *key, - const char *value, - bool invalid_tokens, - bool equal_token, - bool quotes, - unsigned int *curtok) + virtual SMCResult ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) { - return true; + return SMCResult_Continue; } /** * @brief Called after a line has been preprocessed, if it has text. * * @param line Contents of line. + * @param lineno The line number it occurs on. * @param curtok Pointer to optionally store failed position in string. - * @return True to keep parsing, false otherwise. + * @return SMCResult directive. */ - virtual bool ReadINI_RawLine(const char *line, unsigned int *curtok) + virtual SMCResult ReadINI_RawLine(const char *line, unsigned int lineno, unsigned int *curtok) { - return true; + return SMCResult_Continue; } }; @@ -189,35 +227,6 @@ namespace SourceMod * // * / * */ - /** - * @brief Lists actions to take when an SMC parse hook is done. - */ - enum SMCResult - { - SMCResult_Continue, /**< Continue parsing */ - SMCResult_Halt, /**< Stop parsing here */ - SMCResult_HaltFail /**< Stop parsing and return SMCError_Custom */ - }; - - /** - * @brief Lists error codes possible from parsing an SMC file. - */ - enum SMCError - { - SMCError_Okay = 0, /**< No error */ - SMCError_StreamOpen, /**< Stream failed to open */ - SMCError_StreamError, /**< The stream died... somehow */ - SMCError_Custom, /**< A custom handler threw an error */ - SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ - SMCError_InvalidSection2, /**< A section was declared without any header */ - SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ - SMCError_InvalidSection4, /**< A section ending has no matching beginning */ - SMCError_InvalidSection5, /**< A section beginning has no matching ending */ - SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ - SMCError_TokenOverflow, /**< The token buffer overflowed */ - SMCError_InvalidProperty1, /**< A property was declared outside of any section */ - }; - /** * @brief States for line/column */ @@ -341,9 +350,9 @@ namespace SourceMod * @param ini_listener Event handler for reading file. * @param line If non-NULL, will contain last line parsed (0 if file could not be opened). * @param col If non-NULL, will contain last column parsed (undefined if file could not be opened). - * @return True if parsing succeeded, false if file couldn't be opened or there was a syntax error. + * @return An SMCError result code. */ - virtual bool ParseFile_INI(const char *file, + virtual SMCError ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) =0; diff --git a/support/PackageScript b/support/PackageScript index f3524d0d..5185c646 100644 --- a/support/PackageScript +++ b/support/PackageScript @@ -235,6 +235,9 @@ scripting_files = [ 'testsuite/trietest.sma', 'testsuite/utf8test.sma', 'testsuite/stacktest.sma', + 'testsuite/textparse_test.sma', + 'testsuite/textparse_test.cfg', + 'testsuite/textparse_test.ini', 'include/amxconst.inc', 'include/amxmisc.inc', 'include/amxmodx.inc', From 037af0aec281cdc9ac42661e24c039ddda6e75d3 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sun, 3 Aug 2014 20:43:32 +0200 Subject: [PATCH 03/14] Split INI/SMC API. --- amxmodx/CTextParsers.cpp | 93 +++---- amxmodx/CTextParsers.h | 42 +-- amxmodx/textparse.cpp | 377 ++++++++++++++++++--------- amxmodx/textparse.h | 119 ++++----- plugins/include/amxmodx.inc | 2 + plugins/include/textparse.inc | 316 ---------------------- plugins/include/textparse_ini.inc | 196 ++++++++++++++ plugins/include/textparse_smc.inc | 242 +++++++++++++++++ plugins/testsuite/textparse_test.sma | 80 ++++-- public/ITextParsers.h | 119 ++++----- 10 files changed, 897 insertions(+), 689 deletions(-) delete mode 100644 plugins/include/textparse.inc create mode 100644 plugins/include/textparse_ini.inc create mode 100644 plugins/include/textparse_smc.inc diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp index 5ebcfaed..4e79cc87 100644 --- a/amxmodx/CTextParsers.cpp +++ b/amxmodx/CTextParsers.cpp @@ -1,33 +1,13 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* This program is free software; you can redistribute it and/or modify it under -* the terms of the GNU General Public License, version 3.0, as published by the -* Free Software Foundation. -* -* 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 General Public License for more -* details. -* -* You should have received a copy of the GNU General Public License along with -* this program. If not, see . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id$ -*/ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. + * + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license + */ #include "amxmodx.h" #include "CTextParsers.h" @@ -778,7 +758,7 @@ failed: * INI parser */ -SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) +bool TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) { FILE *fp = fopen(file, "rt"); unsigned int curline = 0; @@ -792,7 +772,7 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis *line = 0; } - return SMCError_StreamOpen; + return false; } ini_listener->ReadINI_ParseStart(); @@ -800,8 +780,6 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis char buffer[2048]; char *ptr, *save_ptr; bool in_quote; - SMCError err = SMCError_Okay; - SMCResult res = SMCResult_Continue; while (!feof(fp)) { @@ -853,22 +831,24 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis { switch (*ptr) { - case '"': - { - in_quote = true; - break; - } - case ';': - { - /* Stop the loop */ - len = i; - /* Terminate the string here */ - *ptr = '\0'; - break; - } + case '"': + { + in_quote = true; + break; + } + case ';': + { + /* Stop the loop */ + len = i; + + /* Terminate the string here */ + *ptr = '\0'; + break; + } } } - else { + else + { if (*ptr == '"') { in_quote = false; @@ -901,9 +881,8 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis continue; } - if ((res = ini_listener->ReadINI_RawLine(ptr, curline, &curtok)) != SMCResult_Continue) + if (!ini_listener->ReadINI_RawLine(ptr, &curtok)) { - err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; goto event_failed; } @@ -956,9 +935,8 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis } /* Tell the handler */ - if ((res = ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok)) != SMCResult_Continue) + if (!ini_listener->ReadINI_NewSection(&ptr[1], invalid_tokens, got_bracket, extra_tokens, &curtok)) { - err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; goto event_failed; } } @@ -1063,9 +1041,8 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis else curtok = 0; - if ((res = ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok)) != SMCResult_Continue) + if (!ini_listener->ReadINI_KeyValue(key_ptr, val_ptr, invalid_tokens, equal_token, quotes, &curtok)) { - err = (res == SMCResult_HaltFail) ? SMCError_Custom : SMCError_Okay; curtok = 0; goto event_failed; } @@ -1084,9 +1061,9 @@ SMCError TextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_lis fclose(fp); - ini_listener->ReadINI_ParseEnd(false, false); + ini_listener->ReadINI_ParseEnd(false); - return SMCError_Okay; + return true; event_failed: if (line) @@ -1101,9 +1078,9 @@ event_failed: fclose(fp); - ini_listener->ReadINI_ParseEnd(true, (err == SMCError_Custom)); + ini_listener->ReadINI_ParseEnd(true); - return err; + return false; } const char *TextParsers::GetSMCErrorString(SMCError err) diff --git a/amxmodx/CTextParsers.h b/amxmodx/CTextParsers.h index 7aaafb63..8ceeca6d 100644 --- a/amxmodx/CTextParsers.h +++ b/amxmodx/CTextParsers.h @@ -1,33 +1,13 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* This program is free software; you can redistribute it and/or modify it under -* the terms of the GNU General Public License, version 3.0, as published by the -* Free Software Foundation. -* -* 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 General Public License for more -* details. -* -* You should have received a copy of the GNU General Public License along with -* this program. If not, see . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id$ -*/ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. + * + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license + */ #ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ #define _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ @@ -51,7 +31,7 @@ class TextParsers : public ITextParsers public: TextParsers(); public: - SMCError ParseFile_INI(const char *file, + bool ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col); diff --git a/amxmodx/textparse.cpp b/amxmodx/textparse.cpp index b34bdc61..e72efed0 100644 --- a/amxmodx/textparse.cpp +++ b/amxmodx/textparse.cpp @@ -1,33 +1,13 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* This program is free software; you can redistribute it and/or modify it under -* the terms of the GNU General Public License, version 3.0, as published by the -* Free Software Foundation. -* -* 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 General Public License for more -* details. -* -* You should have received a copy of the GNU General Public License along with -* this program. If not, see . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id$ -*/ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. + * + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license + */ #include "amxmodx.h" #include @@ -35,16 +15,41 @@ TextParserHandles g_TextParsersHandles; -static cell AMX_NATIVE_CALL SMC_CreateParser(AMX *amx, cell *params) +cell createParser() { - int handle = g_TextParsersHandles.create(); - ParseInfo *p = g_TextParsersHandles.lookup(handle); - - p->ini_format = params[1] > 0 ? true : false; - - return static_cast(handle); + return g_TextParsersHandles.create(); } +cell destroyParser(cell *handle) +{ + ParseInfo *p = g_TextParsersHandles.lookup(*handle); + + if (p == NULL) + { + return 0; + } + + if (g_TextParsersHandles.destroy(*handle)) + { + *handle = 0; + return 1; + } + + return 0; +} + + +/** + * SMC CONFIG. + */ + +// native SMCParser:SMC_CreateParser(); +static cell AMX_NATIVE_CALL SMC_CreateParser(AMX *amx, cell *params) +{ + return createParser(); +} + +// native SMC_SetParseStart(SMCParser:handle, const func[]); static cell AMX_NATIVE_CALL SMC_SetParseStart(AMX *amx, cell *params) { ParseInfo *p = g_TextParsersHandles.lookup(params[1]); @@ -55,21 +60,24 @@ static cell AMX_NATIVE_CALL SMC_SetParseStart(AMX *amx, cell *params) return 0; } - int length; - const char* functionName = get_amxstring(amx, params[2], 0, length); - int function = registerSPForwardByName(amx, functionName, FP_CELL, FP_DONE); + int length = 0; + const char *funcName = NULL; - if (function == -1) + if ((funcName = get_amxstring(amx, params[2], 0, length)) && length) { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", functionName, g_plugins.findPluginFast(amx)->getName()); - return 0; + p->parse_start = registerSPForwardByName(amx, funcName, FP_CELL, FP_DONE); } - p->parse_start = function; + if (p->parse_start == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } return 1; } +// native SMC_SetParseEnd(SMCParser:handle, const func[]); static cell AMX_NATIVE_CALL SMC_SetParseEnd(AMX *amx, cell *params) { ParseInfo *p = g_TextParsersHandles.lookup(params[1]); @@ -80,21 +88,24 @@ static cell AMX_NATIVE_CALL SMC_SetParseEnd(AMX *amx, cell *params) return 0; } - int length; - const char* funcName = get_amxstring(amx, params[2], 0, length); - int func = registerSPForwardByName(amx, funcName, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + int length = 0; + const char *funcName = NULL; - if (func == -1) + if ((funcName = get_amxstring(amx, params[2], 0, length)) && length) + { + p->parse_end = registerSPForwardByName(amx, funcName, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + } + + if (p->parse_end == -1) { LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); return 0; } - p->parse_end = func; - return 1; } +// native SMC_SetReaders(SMCParser:smc, const kvFunc[], const nsFunc[] = "", const esFunc[] = ""); static cell AMX_NATIVE_CALL SMC_SetReaders(AMX *amx, cell *params) { ParseInfo *p = g_TextParsersHandles.lookup(params[1]); @@ -105,55 +116,34 @@ static cell AMX_NATIVE_CALL SMC_SetReaders(AMX *amx, cell *params) return 0; } - int length; - const char* newSectionFuncName = get_amxstring(amx, params[2], 0, length); - const char* keyValueFuncName = get_amxstring(amx, params[3], 1, length); - const char* endSectionFuncName = get_amxstring(amx, params[4], 2, length); + int kvLength = 0, nsLength = 0, esLength = 0; + const char *funcName = NULL; - int newSectionFunc; - int keyValueFunc; - int endSectionFunc; - - if (p->ini_format) - newSectionFunc = registerSPForwardByName(amx, newSectionFuncName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); - else - newSectionFunc = registerSPForwardByName(amx, newSectionFuncName, FP_CELL, FP_STRING, FP_DONE); - - if (newSectionFunc == -1) + if ((funcName = get_amxstring(amx, params[2], 0, kvLength)) && kvLength) { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", newSectionFuncName, g_plugins.findPluginFast(amx)->getName()); + p->key_value = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_STRING, FP_DONE); + } + + if (kvLength && (funcName = get_amxstring(amx, params[3], 1, nsLength)) && nsLength) + { + p->new_section = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_DONE); + } + + if (kvLength && (funcName = get_amxstring(amx, params[4], 2, esLength)) && esLength) + { + p->end_section = registerSPForwardByName(amx, funcName, FP_CELL, FP_DONE); + } + + if (p->key_value == -1 || (nsLength && p->new_section == -1) || (esLength && p->end_section == -1)) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); return 0; } - if (p->ini_format) - keyValueFunc = registerSPForwardByName(amx, keyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); - else - keyValueFunc = registerSPForwardByName(amx, keyValueFuncName, FP_CELL, FP_STRING, FP_STRING, FP_DONE); - - if (keyValueFunc == -1) - { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", keyValueFuncName, g_plugins.findPluginFast(amx)->getName()); - return 0; - } - - if (!p->ini_format) - { - endSectionFunc = registerSPForwardByName(amx, endSectionFuncName, FP_CELL, FP_DONE); - if (endSectionFunc == -1) - { - LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", endSectionFuncName, g_plugins.findPluginFast(amx)->getName()); - return 0; - } - - p->end_section = endSectionFunc; - } - - p->new_section = newSectionFunc; - p->key_value = keyValueFunc; - return 1; } +// native SMC_SetRawLine(SMCParser:handle, const func[]); static cell AMX_NATIVE_CALL SMC_SetRawLine(AMX *amx, cell *params) { ParseInfo *p = g_TextParsersHandles.lookup(params[1]); @@ -164,26 +154,24 @@ static cell AMX_NATIVE_CALL SMC_SetRawLine(AMX *amx, cell *params) return 0; } - int length; - const char* funcName = get_amxstring(amx, params[2], 0, length); + int length = 0; + const char *funcName = NULL; - int func; - if (p->ini_format) - func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_DONE); - else - func = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_DONE); + if ((funcName = get_amxstring(amx, params[2], 0, length)) && length) + { + p->raw_line = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_DONE); + } - if (func == -1) + if (p->raw_line == -1) { LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); return 0; } - p->raw_line = func; - return 1; } +// native SMCError:SMC_ParseFile(SMCParser:handle, const file[], &line = 0, &col = 0); static cell AMX_NATIVE_CALL SMC_ParseFile(AMX *amx, cell *params) { ParseInfo *p = g_TextParsersHandles.lookup(params[1]); @@ -197,28 +185,16 @@ static cell AMX_NATIVE_CALL SMC_ParseFile(AMX *amx, cell *params) int length; const char *file = build_pathname("%s", get_amxstring(amx, params[2], 0, length)); - SMCError p_err; + SMCStates states; + SMCError p_err = textparsers->ParseFile_SMC(file, p, &states); - if (p->ini_format) - { - size_t line, col; - p_err = textparsers->ParseFile_INI(file, p, &line, &col); - - *get_amxaddr(amx, params[3]) = line; - *get_amxaddr(amx, params[4]) = col; - } - else - { - SMCStates states; - p_err = textparsers->ParseFile_SMC(file, p, &states); - - *get_amxaddr(amx, params[3]) = states.line; - *get_amxaddr(amx, params[4]) = states.col; - } + *get_amxaddr(amx, params[3]) = states.line; + *get_amxaddr(amx, params[4]) = states.col; return static_cast(p_err); } +// native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); static cell AMX_NATIVE_CALL SMC_GetErrorString(AMX *amx, cell *params) { const char *str = textparsers->GetSMCErrorString((SMCError)params[1]); @@ -231,26 +207,170 @@ static cell AMX_NATIVE_CALL SMC_GetErrorString(AMX *amx, cell *params) return set_amxstring(amx, params[2], str, params[3]); } +// native SMC_DestroyParser(&SMCParser:handle); static cell AMX_NATIVE_CALL SMC_DestroyParser(AMX *amx, cell *params) { - cell *ptr = get_amxaddr(amx, params[1]); + return destroyParser(get_amxaddr(amx, params[1])); +} - ParseInfo *p = g_TextParsersHandles.lookup(*ptr); + +/** + * INI CONFIG + */ + +// native INIParser:INI_CreateParser(); +static cell AMX_NATIVE_CALL INI_CreateParser(AMX *amx, cell *params) +{ + return createParser(); +} + +// native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); +static cell AMX_NATIVE_CALL INI_ParseFile(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); if (p == NULL) { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); return 0; } - if (g_TextParsersHandles.destroy(*ptr)) + int length; + const char *file = build_pathname("%s", get_amxstring(amx, params[2], 0, length)); + + size_t line, col; + bool result = textparsers->ParseFile_INI(file, p, &line, &col); + + *get_amxaddr(amx, params[3]) = line; + *get_amxaddr(amx, params[4]) = col; + + return result; +} + +// native INI_SetParseStart(INIParser:handle, const func[]); +static cell AMX_NATIVE_CALL INI_SetParseStart(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) { - *ptr = 0; - return 1; + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; } - return 0; + int length = 0; + const char *funcName = NULL; + + if ((funcName = get_amxstring(amx, params[2], 0, length)) && length) + { + p->parse_start = registerSPForwardByName(amx, funcName, FP_CELL, FP_DONE); + } + + if (p->parse_start == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + return 1; } +// native INI_SetParseEnd(INIParser:handle, const func[]); +static cell AMX_NATIVE_CALL INI_SetParseEnd(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length = 0; + const char *funcName = NULL; + + if ((funcName = get_amxstring(amx, params[2], 0, length))) + { + p->parse_end = registerSPForwardByName(amx, funcName, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + } + + if (p->parse_end == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + return 1; +} + +// native INI_SetReaders(INIParser:smc, const kvFunc[], const nsFunc[] = "" ); +static cell AMX_NATIVE_CALL INI_SetReaders(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int kvLength = 0, nsLength = 0; + const char *funcName = NULL; + + if ((funcName = get_amxstring(amx, params[2], 0, kvLength)) && kvLength) + { + p->key_value = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + } + + if (kvLength && (funcName = get_amxstring(amx, params[3], 1, nsLength)) && nsLength) + { + p->new_section = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_CELL, FP_CELL, FP_DONE); + } + + if (p->key_value == -1 || (nsLength && p->new_section == -1)) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + return 1; +} + +// native INI_SetRawLine(INIParser:handle, const func[]); +static cell AMX_NATIVE_CALL INI_SetRawLine(AMX *amx, cell *params) +{ + ParseInfo *p = g_TextParsersHandles.lookup(params[1]); + + if (p == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int length = 0; + const char *funcName = NULL; + + if ((funcName = get_amxstring(amx, params[2], 0, length)) && length) + { + p->raw_line = registerSPForwardByName(amx, funcName, FP_CELL, FP_STRING, FP_CELL, FP_CELL, FP_DONE); + } + + if (p->raw_line == -1) + { + LogError(amx, AMX_ERR_NATIVE, "Function is not present (function \"%s\") (plugin \"%s\")", funcName, g_plugins.findPluginFast(amx)->getName()); + return 0; + } + + return 1; +} + +// native INI_DestroyParser(&INIParser:handle); +static cell AMX_NATIVE_CALL INI_DestroyParser(AMX *amx, cell *params) +{ + return destroyParser(get_amxaddr(amx, params[1])); +} + + AMX_NATIVE_INFO g_TextParserNatives[] = { { "SMC_CreateParser" , SMC_CreateParser }, @@ -261,5 +381,14 @@ AMX_NATIVE_INFO g_TextParserNatives[] = { "SMC_SetReaders" , SMC_SetReaders }, { "SMC_SetRawLine" , SMC_SetRawLine }, { "SMC_DestroyParser" , SMC_DestroyParser }, + + { "INI_CreateParser" , INI_CreateParser }, + { "INI_ParseFile" , INI_ParseFile }, + { "INI_SetParseStart" , INI_SetParseStart }, + { "INI_SetParseEnd" , INI_SetParseEnd }, + { "INI_SetReaders" , INI_SetReaders }, + { "INI_SetRawLine" , INI_SetRawLine }, + { "INI_DestroyParser" , INI_DestroyParser }, + { NULL, NULL }, }; diff --git a/amxmodx/textparse.h b/amxmodx/textparse.h index 10b55dd3..93d3ffc9 100644 --- a/amxmodx/textparse.h +++ b/amxmodx/textparse.h @@ -1,33 +1,13 @@ -/** -* vim: set ts=4 : -* ============================================================================= -* SourceMod -* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. -* ============================================================================= -* -* This program is free software; you can redistribute it and/or modify it under -* the terms of the GNU General Public License, version 3.0, as published by the -* Free Software Foundation. -* -* 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 General Public License for more -* details. -* -* You should have received a copy of the GNU General Public License along with -* this program. If not, see . -* -* As a special exception, AlliedModders LLC gives you permission to link the -* code of this program (as well as its derivative works) to "Half-Life 2," the -* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software -* by the Valve Corporation. You must obey the GNU General Public License in -* all respects for all other code used. Additionally, AlliedModders LLC grants -* this exception to all derivative works. AlliedModders LLC defines further -* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), -* or . -* -* Version: $Id$ -*/ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. + * + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license + */ #ifndef _INCLUDE_TEXTPARSE_H_ #define _INCLUDE_TEXTPARSE_H_ @@ -43,37 +23,31 @@ public: ParseInfo() { parse_start = -1; - parse_end = -1; + parse_end = -1; new_section = -1; - key_value = -1; + key_value = -1; end_section = -1; - raw_line = -1; - handle = -1; - ini_format = false; + raw_line = -1; + handle = -1; } public: + + /** + * SMC CONFIG. + */ + void ReadSMC_ParseStart() { if (parse_start != -1) executeForwards(parse_start, handle); } - void ReadINI_ParseStart() - { - if (parse_start != -1) - executeForwards(parse_start, handle); - } void ReadSMC_ParseEnd(bool halted, bool failed) { if (parse_end != -1) executeForwards(parse_end, handle, halted ? 1 : 0, failed ? 1 : 0); } - void ReadINI_ParseEnd(bool halted, bool failed) - { - if (parse_end != -1) - executeForwards(parse_end, handle, halted ? 1 : 0, failed ? 1 : 0); - } SMCResult ReadSMC_NewSection(const SMCStates *states, const char *name) { @@ -82,13 +56,6 @@ public: return SMCResult_Continue; } - SMCResult ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) - { - if (new_section != -1) - return (SMCResult)executeForwards(new_section, handle, section, invalid_tokens, close_bracket, extra_tokens, *curtok); - - return SMCResult_Continue; - } SMCResult ReadSMC_KeyValue(const SMCStates *states, const char *key, const char *value) { @@ -97,13 +64,6 @@ public: return SMCResult_Continue; } - SMCResult ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) - { - if (key_value != -1) - return (SMCResult)executeForwards(key_value, handle, key, value, invalid_tokens, equal_token, quotes, *curtok); - - return SMCResult_Continue; - } SMCResult ReadSMC_LeavingSection(const SMCStates *states) { @@ -120,12 +80,46 @@ public: return SMCResult_Continue; } - SMCResult ReadINI_RawLine(const char *line, unsigned int lineno, unsigned int *curtok) + + + /** + * INI CONFIG. + */ + + void ReadINI_ParseStart() + { + if (parse_start != -1) + executeForwards(parse_start, handle); + } + + void ReadINI_ParseEnd(bool halted) + { + if (parse_end != -1) + executeForwards(parse_end, handle, halted ? 1 : 0); + } + + bool ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) + { + if (new_section != -1) + return executeForwards(new_section, handle, section, invalid_tokens, close_bracket, extra_tokens, *curtok) > 0 ? false : true; + + return true; + } + + bool ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) + { + if (key_value != -1) + return executeForwards(key_value, handle, key, value, invalid_tokens, equal_token, quotes, *curtok) > 0 ? false : true; + + return true; + } + + bool ReadINI_RawLine(const char *line, unsigned int *curtok) { if (raw_line != -1) - return (SMCResult)executeForwards(raw_line, handle, line, lineno, *curtok); + return executeForwards(raw_line, handle, line, *curtok) > 0 ? false : true; - return SMCResult_Continue; + return true; } public: int parse_start; @@ -135,7 +129,6 @@ public: int end_section; int raw_line; int handle; - bool ini_format; }; template diff --git a/plugins/include/amxmodx.inc b/plugins/include/amxmodx.inc index 36503ce0..cedbd6d6 100755 --- a/plugins/include/amxmodx.inc +++ b/plugins/include/amxmodx.inc @@ -27,6 +27,8 @@ #include #include #include +#include +#include /* Function is called just after server activation. * Good place for configuration loading, commands and cvars registration. */ diff --git a/plugins/include/textparse.inc b/plugins/include/textparse.inc deleted file mode 100644 index d7eef5ef..00000000 --- a/plugins/include/textparse.inc +++ /dev/null @@ -1,316 +0,0 @@ - -#if defined _textparse_included - #endinput -#endif -#define _textparse_included - -/** - * This parser API is entirely event based. You must hook events to receive data. - * The file format can ben either INI or SMC (also known as "SourceMod Configuration". - * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). - * Also please note INI format is handled differently. Because more "simple" to parse, some - * event doesn't exist and callback prototype can be different. - */ - -/** - * The INI file format is defined as: - * WHITESPACE: 0x20, \n, \t, \r - * IDENTIFIER: A-Z a-z 0-9 _ - , + . $ ? / - * STRING : Any set of symbols - * - * Basic syntax is comprised of SECTIONs. - * A SECTION is defined as: - * [SECTIONNAME] - * OPTION - * OPTION - * OPTION... - * - * SECTIONNAME is an IDENTIFIER. - * OPTION can be repeated any number of times, once per line. - * OPTION is defined as one of: - * KEY = "VALUE" - * KEY = VALUE - * KEY - * Where KEY is an IDENTIFIER and VALUE is a STRING. - * - * WHITESPACE should always be omitted. - * COMMENTS should be stripped, and are defined as text occurring in: - * ; - * - * Example file below. Note that the second line is technically invalid. - * The event handler must decide whether this should be allowed. - * --FILE BELOW-- - * [gaben] - * hi = clams - * bye = "NO CLAMS" - * - * [valve] - * cannot - * maintain - * products - */ - -/** - * The SMC file format is defined as: - * WHITESPACE: 0x20, \n, \t, \r - * IDENTIFIER: Any ASCII character EXCLUDING ", {, }, ;, //, / *, or WHITESPACE. - * STRING : Any set of symbols enclosed in quotes. - * - * Note: if a STRING does not have quotes, it is parsed as an IDENTIFIER. - * - * Basic syntax is comprised of SECTIONBLOCKs. - * A SECTIONBLOCK defined as: - * - * SECTIONNAME - * { - * OPTION - * } - * - * OPTION can be repeated any number of times inside a SECTIONBLOCK. - * A new line will terminate an OPTION, but there can be more than one OPTION per line. - * OPTION is defined any of: - * "KEY" "VALUE" - * SECTIONBLOCK - * - * SECTIONNAME, KEY, VALUE, and SINGLEKEY are strings - * SECTIONNAME cannot have trailing characters if quoted, but the quotes can be optionally removed. - * If SECTIONNAME is not enclosed in quotes, the entire sectionname string is used (minus surrounding whitespace). - * If KEY is not enclosed in quotes, the key is terminated at first whitespace. - * If VALUE is not properly enclosed in quotes, the entire value string is used (minus surrounding whitespace). - * The VALUE may have inner quotes, but the key string may not. - * - * For an example, see scripting/testsuite/textparse_test.cfg - * - * WHITESPACE should be ignored. - * Comments are text occurring inside the following tokens, and should be stripped - * unless they are inside literal strings: - * ; - * // - * / * * / - */ - -enum TextParser -{ - Invalid_TextParser = 0 -}; - -/** - * Parse result directive. - */ -enum SMCResult -{ - SMCParse_Continue, /**< Continue parsing */ - SMCParse_Halt, /**< Stop parsing here */ - SMCParse_HaltFail /**< Stop parsing and return failure */ -}; - -/** - * Parse error codes. - */ -enum SMCError -{ - SMCError_Okay = 0, /**< No error */ - SMCError_StreamOpen, /**< Stream failed to open */ - SMCError_StreamError, /**< The stream died... somehow */ - SMCError_Custom, /**< A custom handler threw an error */ - SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ - SMCError_InvalidSection2, /**< A section was declared without any header */ - SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ - SMCError_InvalidSection4, /**< A section ending has no matching beginning */ - SMCError_InvalidSection5, /**< A section beginning has no matching ending */ - SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ - SMCError_TokenOverflow, /**< The token buffer overflowed */ - SMCError_InvalidProperty1, /**< A property was declared outside of any section */ -}; - - -/** - * Creates a new text parser. - * This is used to set parse hooks. - * - * @param ini_format Sets whether INI format or SMC should be used. - * - * @return A new handle to an Text Parse structure. - */ -native TextParser:SMC_CreateParser(bool:ini_format = false); - -/** - * Disposes of a text parser. - * - * @param handle Handle to a Text Parse. - * @return True if disposed, false otherwise. - */ -native SMC_DestroyParser(&TextParser:handle); - -/** - * Parses a config file. - * - * @note If using INI format, you can expect only the following SMCError_* constants: - * SMCError_Okay, SMCError_StreamOpen and SMCError_Custom. - * - * @param handle A handle to an Text Parse structure. - * @param file A string containing the file path. - * @param line An optional by reference cell to store the last line number read. - * @param col An optional by reference cell to store the last column number read. - - * @return A SMCParseError result. - * @error Invalid or corrupt handle. - */ -native SMCError:SMC_ParseFile(TextParser:handle, const file[], &line = 0, &col = 0); - -/** - * Sets the SMC_ParseStart function of a parse handle. - * Below is the prototype of callback.: - * - * - ParseStart: - * Called when parsing is started. - * - * @param handle The Text Parse handle. - * @noreturn - * - * public SMC_ParseStart(TextParser:handle) - * - * @param handle Handle to a Text Parse. - * @param func A ParseStart callback. - * - * @noreturn - * @error Invalid or corrupt handle. - */ -native SMC_SetParseStart(TextParser:handle, const func[]); - -/** - * Sets the SMC_ParseEnd of a parse handle. - * Below is the prototype of callback.: - * - * - ParseEnd: - * Called when parsing is halted. - * - * @param handle The Text Parse handle. - * @param halted True if abnormally halted, false otherwise. - * @param failed True if parsing failed, false otherwise. - * If using INI format, this is only true when - * a callback returns SMCResult_HaltFail. - * @noreturn - * - * public SMC_ParseEnd(TextParser:handle, bool:halted, bool:failed) - * - * @param handle Handle to a Text Parse. - * @param func A ParseEnd callback.. - * - * @noreturn - * @error Invalid or corrupt handle. - */ -native SMC_SetParseEnd(TextParser:handle, const func[]); - -/** - * Sets the three main reader functions. - * Below are the different prototypes of callbacks: - * - * - NewSection: - * Called when the parser finds the end of the current section. - * - * INI: - * @param section Name of section in between the [ and ] characters. - * @param invalid_tokens True if invalid tokens were detected in the name. - * @param close_bracket True if a closing bracket was detected, false otherwise. - * @param extra_tokens True if extra tokens were detected on the line. - * @param curtok Contains current token in the line where the section name starts. - * can add to this offset when failing to point to a token. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_NewSection(TextParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) - * - * SMC: - * @note Enclosing quotes are always stripped. - * @param handle The Text Parse handle. - * @param name String containing section name. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_NewSection(TextParser:handle, const name[]) - * - * - KeyValue: - * Called when the parser finds a new key/value pair. - * - * INI: - * @param handle The Text Parse handle. - * @param key Name of key. - * @param value String containing value (with quotes stripped, if any). - * @param invalid_tokens Whether or not the key contained invalid tokens. - * @param equal_token There was an '=' sign present (in case the value is missing). - * @param quotes Whether value was enclosed in quotes. - * @param curtok Contains the token index of the start of the value string. - * This can be changed when returning false. - * @return SMCResult directive. - * - * public SMCResult:SMC_KeyValue(TextParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) - * - * SMC: - * @note Enclosing quotes are always stripped. - * - * @param handle The Text Parse handle. - * @param key String containing key name. - * @param value String containing value name. - * @return A SMCResult action to take. - * - * public SMCResult:SMC_KeyValue(TextParser:handle, const key[], const value[]) - * - * - EndSection: - * Called when the parser finds the end of the current section. - * Only with SMC format. - - * @param handle The Text Parse handle. - * - * public SMCResult:SMC_EndSection(TextParser:handle) - * - - * @param handle The Text parse handle. - * @param ns A NewSection callback. - * @param kv A KeyValue callback. - * @param es An EndSection callback. Only available for SMC config format. - * - * @noreturn - */ -native SMC_SetReaders(TextParser:smc, const nsFunc[], const kvFunc[], const esFunc[] = ""); - -/** - * Sets a raw line reader on an text parser handle. - * Below is the prototype of callback.: - * - * - RawLine: - * Called whenever a raw line is read. - * - * INI: - * @param handle The Text Parse handle. - * @param line Contents of line. - * @param lineno The line number it occurs on. - * @param curtok Pointer to optionally store failed position in string. - * @return SMCResult directive. - * - * public SMCResult:SMC_RawLine(TextParser:smc, const line[], lineno, curtok) - * - * SMC: - * @param handle The Text Parse handle. - * @param line A string containing the raw line from the file. - * @param lineno The line number it occurs on. - * @return An SMCResult action to take. - * - * public SMCResult:SMC_RawLine(TextParser:handle, const line[], lineno) - * - * @param handle Handle to an Text Parse. - * @param func A RawLine callback. - * @noreturn - */ -native SMC_SetRawLine(TextParser:handle, const func[]); - -/** - * Gets an error string for an SMCError code. - * - * @note SMCError_Okay returns false. - * @note SMCError_Custom (which is thrown on SMCParse_HaltFail) returns false. - * - * @param error The SMCParseError code. - * @param buffer A string buffer for the error (contents undefined on failure). - * @param buf_max The maximum size of the buffer. - * - * @return True on success, false otherwise. - */ -native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); \ No newline at end of file diff --git a/plugins/include/textparse_ini.inc b/plugins/include/textparse_ini.inc new file mode 100644 index 00000000..f5891a1c --- /dev/null +++ b/plugins/include/textparse_ini.inc @@ -0,0 +1,196 @@ + +#if defined _textparse_ini_included + #endinput +#endif +#define _textparse_ini_included + +/** + * This parser API is entirely event based. You must hook events to receive data. + * The file format can ben either INI or SMC (also known as "SourceMod Configuration". + * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). + * Also please note INI format is handled differently. Because more "simple" to parse, some + * event doesn't exist and callback prototype can be different. + */ + +/** + * The INI file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: A-Z a-z 0-9 _ - , + . $ ? / + * STRING : Any set of symbols + * + * Basic syntax is comprised of SECTIONs. + * A SECTION is defined as: + * [SECTIONNAME] + * OPTION + * OPTION + * OPTION... + * + * SECTIONNAME is an IDENTIFIER. + * OPTION can be repeated any number of times, once per line. + * OPTION is defined as one of: + * KEY = "VALUE" + * KEY = VALUE + * KEY + * Where KEY is an IDENTIFIER and VALUE is a STRING. + * + * WHITESPACE should always be omitted. + * COMMENTS should be stripped, and are defined as text occurring in: + * ; + * + * Example file below. Note that the second line is technically invalid. + * The event handler must decide whether this should be allowed. + * --FILE BELOW-- + * [gaben] + * hi = clams + * bye = "NO CLAMS" + * + * [valve] + * cannot + * maintain + * products + */ + +/** + * Parser invalid code. + */ +enum INIParser +{ + Invalid_INIParser = 0 +}; + +/** + * Creates a new INI parser. + * This is used to set parse hooks. + * + * @return A new handle to an INI Parse structure. + */ +native INIParser:INI_CreateParser(); + +/** + * Disposes of a INI parser. + * + * @param handle Handle to a INI Parse. + * + * @return True if disposed, false otherwise. + */ +native INI_DestroyParser(&INIParser:handle); + +/** + * Parses a INI config file. + * + * @param handle A handle to an INI Parse structure. + * @param file A string containing the file path. + * @param line An optional by reference cell to store the last line number read. + * @param col An optional by reference cell to store the last column number read. + + * @return A SMCParseError result. + * @error Invalid or corrupt handle. + */ +native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); + +/** + * Sets the INI_ParseStart function of a parse handle. + * + * @note Below is the prototype of callback: + * - + * Called when parsing is started. + * + * @param handle The INI Parse handle. + * + * @noreturn + * + * public OnParseStart(INIParser:handle) + * - + * @param handle Handle to a INI Parse. + * @param func A ParseStart callback. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native INI_SetParseStart(INIParser:handle, const func[]); + +/** + * Sets the INI_ParseEnd of a parse handle. + * + * @note Below is the prototype of callback: + * - + * Called when parsing is halted. + * + * @param handle The INI Parse handle. + * @param halted True if abnormally halted, false otherwise. + * + * @noreturn + * + * public OnParseEnd(INIParser:handle, bool:halted) + * - + * @param handle Handle to a INI Parse. + * @param func A ParseEnd callback.. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native INI_SetParseEnd(INIParser:handle, const func[]); + +/** + * Sets the three main reader functions. + * + * @note Below is the prototype of callback: + * - + * NewSection: + * Called when the parser finds the end of the current section. + * + * @param section Name of section in between the [ and ] characters. + * @param invalid_tokens True if invalid tokens were detected in the name. + * @param close_bracket True if a closing bracket was detected, false otherwise. + * @param extra_tokens True if extra tokens were detected on the line. + * @param curtok Contains current token in the line where the section name starts. + * can add to this offset when failing to point to a token. + * @return True to keep parsing, false otherwise. + * + * public bool:OnNewSection(INIParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) + * + * KeyValue: + * Called when the parser finds a new key/value pair. + * + * @param handle The INI Parse handle. + * @param key Name of key. + * @param value String containing value (with quotes stripped, if any). + * @param invalid_tokens Whether or not the key contained invalid tokens. + * @param equal_token There was an '=' sign present (in case the value is missing). + * @param quotes Whether value was enclosed in quotes. + * @param curtok Contains the token index of the start of the value string. + * This can be changed when returning false. + * @return True to keep parsing, false otherwise. + * + * public bool:OnKeyValue(INIParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) + * - + * @param handle The INI parse handle. + * @param kv A KeyValue callback. + * @param ns An optional NewSection callback. + * + * @noreturn + */ +native INI_SetReaders(INIParser:smc, const kvFunc[], const nsFunc[] = "" ); + +/** + * Sets a raw line reader on an INI parser handle. + * + * @note Below is the prototype of callback: + * - + * Called whenever a raw line is read. + * + * @param handle The INI Parse handle. + * @param line Contents of line. + * @param lineno The line number it occurs on. + * @param curtok Pointer to optionally store failed position in string. + * + * @return True to keep parsing, false otherwise. + * + * public bool:OnRawLine(INIParser:smc, const line[], lineno, curtok) + * + * @param handle Handle to an INI Parse. + * @param func A RawLine callback. + * + * @noreturn + */ +native INI_SetRawLine(INIParser:handle, const func[]); diff --git a/plugins/include/textparse_smc.inc b/plugins/include/textparse_smc.inc new file mode 100644 index 00000000..3b76102d --- /dev/null +++ b/plugins/include/textparse_smc.inc @@ -0,0 +1,242 @@ + +#if defined _textparse_smc_included + #endinput +#endif +#define _textparse_smc_included + +/** + * This parser API is entirely event based. You must hook events to receive data. + * The file format can ben either INI or SMC (also known as "SourceMod Configuration". + * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). + * Also please note INI format is handled differently. Because more "simple" to parse, some + * event doesn't exist and callback prototype can be different. + */ + +/** + * The SMC file format is defined as: + * WHITESPACE: 0x20, \n, \t, \r + * IDENTIFIER: Any ASCII character EXCLUDING ", {, }, ;, //, / *, or WHITESPACE. + * STRING : Any set of symbols enclosed in quotes. + * + * Note: if a STRING does not have quotes, it is parsed as an IDENTIFIER. + * + * Basic syntax is comprised of SECTIONBLOCKs. + * A SECTIONBLOCK defined as: + * + * SECTIONNAME + * { + * OPTION + * } + * + * OPTION can be repeated any number of times inside a SECTIONBLOCK. + * A new line will terminate an OPTION, but there can be more than one OPTION per line. + * OPTION is defined any of: + * "KEY" "VALUE" + * SECTIONBLOCK + * + * SECTIONNAME, KEY, VALUE, and SINGLEKEY are strings + * SECTIONNAME cannot have trailing characters if quoted, but the quotes can be optionally removed. + * If SECTIONNAME is not enclosed in quotes, the entire sectionname string is used (minus surrounding whitespace). + * If KEY is not enclosed in quotes, the key is terminated at first whitespace. + * If VALUE is not properly enclosed in quotes, the entire value string is used (minus surrounding whitespace). + * The VALUE may have inner quotes, but the key string may not. + * + * For an example, see scripting/testsuite/textparse_test.cfg + * + * WHITESPACE should be ignored. + * Comments are text occurring inside the following tokens, and should be stripped + * unless they are inside literal strings: + * ; + * // + * / * * / + */ + +/** + * Parser invalid code. + */ +enum SMCParser +{ + Invalid_SMCParser = 0 +}; + +/** + * Parse result directive. + */ +enum SMCResult +{ + SMCParse_Continue, /* Continue parsing */ + SMCParse_Halt, /* Stop parsing here */ + SMCParse_HaltFail /* Stop parsing and return failure */ +}; + +/** + * Parse error codes. + */ +enum SMCError +{ + SMCError_Okay = 0, /* No error */ + SMCError_StreamOpen, /* Stream failed to open */ + SMCError_StreamError, /* The stream died... somehow */ + SMCError_Custom, /* A custom handler threw an error */ + SMCError_InvalidSection1, /* A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /* A section was declared without any header */ + SMCError_InvalidSection3, /* A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /* A section ending has no matching beginning */ + SMCError_InvalidSection5, /* A section beginning has no matching ending */ + SMCError_InvalidTokens, /* There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /* The token buffer overflowed */ + SMCError_InvalidProperty1, /* A property was declared outside of any section */ +}; + +/** + * Creates a new SMC parser. + * This is used to set parse hooks. + * + * @return A new handle to an SMC Parse structure. + */ +native SMCParser:SMC_CreateParser(); + +/** + * Disposes of a SMC parser. + * + * @param handle Handle to a SMC Parse + * + * @return True if disposed, false otherwise. + */ +native SMC_DestroyParser(&SMCParser:handle); + +/** + * Parses a config file. + * + * @param handle A handle to an SMC Parse structure. + * @param file A string containing the file path. + * @param line An optional by reference cell to store the last line number read. + * @param col An optional by reference cell to store the last column number read. + * + * @return A SMCParseError result. + * @error Invalid or corrupt handle. + */ +native SMCError:SMC_ParseFile(SMCParser:handle, const file[], &line = 0, &col = 0); + +/** + * Sets the SMC_ParseStart function of a parse handle. + * + * @note Below is the prototype of callback: + * - + * Called when parsing is started. + * + * @param handle The Text Parse handle. + * + * @noreturn + * + * public OnParseStart(SMCParser:handle) + * - + * @param handle Handle to a Text Parse. + * @param func A ParseStart callback. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native SMC_SetParseStart(SMCParser:handle, const func[]); + +/** + * Sets the SMC_ParseEnd of a parse handle. + * + * @note Below is the prototype of callback: + * - + * Called when parsing is halted. + * + * @param handle The Text Parse handle. + * @param halted True if abnormally halted, false otherwise. + * @param failed True if parsing failed, false otherwise. + * + * @noreturn + * + * public OnParseEnd(SMCParser:handle, bool:halted, bool:failed) + * - + * @param handle Handle to a Text Parse. + * @param func A ParseEnd callback.. + * + * @noreturn + * @error Invalid or corrupt handle. + */ +native SMC_SetParseEnd(SMCParser:handle, const func[]); + +/** + * Sets the three main reader functions. + * + * @note Enclosing quotes are always stripped. + * @note Below is the prototype of callbacks: + * - + * NewSection: + * Called when the parser finds the end of the current section. + * + * @param handle The Text Parse handle. + * @param name String containing section name. + * + * @return An SMCResult action to take. + * + * public SMCResult:OnNewSection(SMCParser:handle, const name[]) + * + * KeyValue: + * Called when the parser finds a new key/value pair. + * + * @param handle The Text Parse handle. + * @param key String containing key name. + * @param value String containing value name. + * + * @return A SMCResult action to take. + * + * public SMCResult:OnKeyValue(SMCParser:handle, const key[], const value[]) + * + * EndSection: + * Called when the parser finds the end of the current section. + * + * @param handle The Text Parse handle. + * + * public SMCResult:OnEndSection(SMCParser:handle) + * - + * @param handle The Text parse handle. + * @param kv A KeyValue callback. + * @param ns An optional NewSection callback. + * @param es An optional EndSection callback. + * + * @noreturn + */ +native SMC_SetReaders(SMCParser:smc, const kvFunc[], const nsFunc[] = "", const esFunc[] = ""); + +/** + * Sets a raw line reader on an text parser handle. + * + * @note Below is the prototype of callbacks: + * - + * Called whenever a raw line is read. + * + * @param handle The Text Parse handle. + * @param line A string containing the raw line from the file. + * @param lineno The line number it occurs on. + * + * @return An SMCResult action to take. + * + * public SMCResult:SMC_RawLine(SMCParser:handle, const line[], lineno) + * - + * @param handle Handle to an Text Parse. + * @param func A RawLine callback. + * + * @noreturn + */ +native SMC_SetRawLine(SMCParser:handle, const func[]); + +/** + * Gets an error string for an SMCError code. + * + * @note SMCError_Okay returns false. + * @note SMCError_Custom (which is thrown on SMCParse_HaltFail) returns false. + * + * @param error The SMCParseError code. + * @param buffer A string buffer for the error (contents undefined on failure). + * @param buf_max The maximum size of the buffer. + * + * @return True on success, false otherwise. + */ +native bool:SMC_GetErrorString(SMCError:error, buffer[], buf_max); diff --git a/plugins/testsuite/textparse_test.sma b/plugins/testsuite/textparse_test.sma index 297aeeb7..0bb37d28 100644 --- a/plugins/testsuite/textparse_test.sma +++ b/plugins/testsuite/textparse_test.sma @@ -1,12 +1,25 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet: + * + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. + * + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license + */ + #include -#include new SuccessCount; new Trie:ExpectedKVData; +new bool:Debug; public plugin_init() { register_concmd("textparse", "ConsoleCommand_TextParse"); + + Debug = !!(plugin_flags() & AMX_FLAG_DEBUG); } public ConsoleCommand_TextParse() @@ -52,9 +65,9 @@ InitializeTextParseSMC() new const expectedKeyValueCount = TrieGetSize(ExpectedKVData); new const expectedLineCount = file_size(configFile, .flag = 1) - 1; - new TextParser:parser = SMC_CreateParser(); + new SMCParser:parser = SMC_CreateParser(); - SMC_SetReaders(parser, "ReadCore_NewSection", "ReadCore_KeyValue", "ReadCore_EndSection"); + SMC_SetReaders(parser, "ReadCore_KeyValue", "ReadCore_NewSection", "ReadCore_EndSection"); SMC_SetParseStart(parser, "ReadCore_ParseStart"); SMC_SetRawLine(parser, "ReadCore_CurrentLine"); SMC_SetParseEnd(parser, "ReadCore_ParseEnd"); @@ -80,18 +93,22 @@ InitializeTextParseSMC() SuccessCount = 0; } -public ReadCore_ParseStart(TextParser:handle) +public ReadCore_ParseStart(SMCParser:handle) { + Debug && server_print("ReadCore_ParseStart"); ++SuccessCount; } -public ReadCore_NewSection(TextParser:handle, const name[]) +public ReadCore_NewSection(SMCParser:handle, const name[]) { + Debug && server_print("^tReadCore_NewSection - %s", name); ++SuccessCount; } -public ReadCore_KeyValue(TextParser:handle, const key[], const value[]) +public ReadCore_KeyValue(SMCParser:handle, const key[], const value[]) { + Debug && server_print("^t^tReadCore_KeyValue - %-32s %s", key, value); + new buffer[128]; if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) { @@ -99,18 +116,21 @@ public ReadCore_KeyValue(TextParser:handle, const key[], const value[]) } } -public ReadCore_EndSection(TextParser:handle) +public ReadCore_EndSection(SMCParser:handle) { + Debug && server_print("^tReadCore_EndSection"); ++SuccessCount; } -public ReadCore_CurrentLine(TextParser:handle, const line[], lineno) +public ReadCore_CurrentLine(SMCParser:handle, const line[], lineno) { - ++SuccessCount; + //Debug && server_print("^t^tReadCore_CurrentLine - %s", line); + ++SuccessCount; } -public ReadCore_ParseEnd(TextParser:handle, bool:halted, bool:failed) -{ +public ReadCore_ParseEnd(SMCParser:handle, bool:halted, bool:failed) +{ + Debug &&server_print("ReadCore_ParseEnd - halted: %s, failed: %s", halted ? "yes" : "no", failed ? "yes" : "no"); ++SuccessCount; } @@ -151,20 +171,19 @@ public InitializeTextParseINI() new const expectedKeyValueCount = TrieGetSize(ExpectedKVData) - expectedSectionCount; new const expectedLineCount = TrieGetSize(ExpectedKVData); // This doesn't include blanck/comments line. - new TextParser:parser = SMC_CreateParser(.ini_format = true); + new INIParser:parser = INI_CreateParser(); - SMC_SetReaders(parser, "ReadCSDM_NewSection", "ReadCSDM_KeyValue"); - SMC_SetParseStart(parser, "ReadCSDM_ParseStart"); - SMC_SetRawLine(parser, "ReadCSDM_CurrentLine"); - SMC_SetParseEnd(parser, "ReadCSDM_ParseEnd"); + INI_SetReaders(parser, "ReadCSDM_KeyValue", "ReadCSDM_NewSection"); + INI_SetParseStart(parser, "ReadCSDM_ParseStart"); + INI_SetRawLine(parser, "ReadCSDM_CurrentLine"); + INI_SetParseEnd(parser, "ReadCSDM_ParseEnd"); new line, col; - new SMCError:err = SMC_ParseFile(parser, configFile, line, col); + new bool:result = INI_ParseFile(parser, configFile, line, col); - if (err != SMCError_Okay) + if (!result) { - new buffer[64]; - server_print("Error: %s", SMC_GetErrorString(err, buffer, charsmax(buffer)) ? buffer : "Fatal parse error"); + server_print("^tFatal parse error"); } if (line == expectedLineCount + 1) @@ -174,17 +193,20 @@ public InitializeTextParseINI() server_print("^tTests successful: %d/%d", SuccessCount, expectedStartEndCount + expectedSectionCount + expectedKeyValueCount + expectedLineCount + 1); - SMC_DestroyParser(parser); + INI_DestroyParser(parser); TrieDestroy(ExpectedKVData); } -public ReadCSDM_ParseStart(TextParser:handle) +public ReadCSDM_ParseStart(INIParser:handle) { + Debug && server_print("ReadCSDM_ParseStart"); ++SuccessCount; } -public ReadCSDM_NewSection(TextParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) +public ReadCSDM_NewSection(INIParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) { + Debug && server_print("^tReadCSDM_NewSection - [%s]", section); + if (TrieKeyExists(ExpectedKVData, section)) { if ((equal(section, "secondary") && !extra_tokens) || @@ -197,8 +219,10 @@ public ReadCSDM_NewSection(TextParser:handle, const section[], bool:invalid_toke } } -public ReadCSDM_KeyValue(TextParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) +public ReadCSDM_KeyValue(INIParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) { + Debug && server_print("^t^tReadCSDM_KeyValue - %-32s %s", key, value); + new buffer[128]; if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) { @@ -211,12 +235,14 @@ public ReadCSDM_KeyValue(TextParser:handle, const key[], const value[], bool:inv } } -public ReadCSDM_CurrentLine(TextParser:handle, const line[], lineno, curtok) +public ReadCSDM_CurrentLine(INIParser:handle, const line[], curtok) { - ++SuccessCount; + //Debug && server_print("^t^tReadCSDM_CurrentLine - %s", line); + ++SuccessCount; } -public ReadCSDM_ParseEnd(TextParser:handle, bool:halted, bool:failed) +public ReadCSDM_ParseEnd(INIParser:handle, bool:halted, bool:failed) { + Debug && server_print("ReadCSDM_ParseStart"); ++SuccessCount; } \ No newline at end of file diff --git a/public/ITextParsers.h b/public/ITextParsers.h index a8ef31b4..2d3dfc5e 100644 --- a/public/ITextParsers.h +++ b/public/ITextParsers.h @@ -1,32 +1,12 @@ -/** - * vim: set ts=4 : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= +/** + * vim: set ts=4 sw=4 tw=99 noet: * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * 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 General Public License for more - * details. + * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). + * Copyright (C) The AMX Mod X Development Team. * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ + * This software is licensed under the GNU General Public License, version 3 or higher. + * Additional exceptions apply. For full license details, see LICENSE.txt or visit: + * https://alliedmods.net/amxmodx-license */ #ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_INTERFACE_H_ @@ -81,35 +61,6 @@ namespace SourceMod * products */ - /** - * @brief Lists actions to take when an SMC parse hook is done. - */ - enum SMCResult - { - SMCResult_Continue, /**< Continue parsing */ - SMCResult_Halt, /**< Stop parsing here */ - SMCResult_HaltFail /**< Stop parsing and return SMCError_Custom */ - }; - - /** - * @brief Lists error codes possible from parsing an SMC file. - */ - enum SMCError - { - SMCError_Okay = 0, /**< No error */ - SMCError_StreamOpen, /**< Stream failed to open */ - SMCError_StreamError, /**< The stream died... somehow */ - SMCError_Custom, /**< A custom handler threw an error */ - SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ - SMCError_InvalidSection2, /**< A section was declared without any header */ - SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ - SMCError_InvalidSection4, /**< A section ending has no matching beginning */ - SMCError_InvalidSection5, /**< A section beginning has no matching ending */ - SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ - SMCError_TokenOverflow, /**< The token buffer overflowed */ - SMCError_InvalidProperty1, /**< A property was declared outside of any section */ - }; - /** * @brief Contains parse events for INI files. */ @@ -135,9 +86,8 @@ namespace SourceMod * @brief Called when ending parsing. * * @param halted True if abnormally halted, false otherwise. - * @param failed True if parsing failed, false otherwise. */ - virtual void ReadINI_ParseEnd(bool halted, bool failed) + virtual void ReadINI_ParseEnd(bool halted) { } @@ -150,11 +100,11 @@ namespace SourceMod * @param extra_tokens True if extra tokens were detected on the line. * @param curtok Contains current token in the line where the section name starts. * You can add to this offset when failing to point to a token. - * @return SMCResult directive. + * @return True to keep parsing, false otherwise. */ - virtual SMCResult ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) + virtual bool ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) { - return SMCResult_Continue; + return true; } /** @@ -167,24 +117,24 @@ namespace SourceMod * @param quotes Whether value was enclosed in quotes. * @param curtok Contains the token index of the start of the value string. * This can be changed when returning false. - * @return SMCResult directive. + * @return True to keep parsing, false otherwise. */ - virtual SMCResult ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) + virtual bool ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) { - return SMCResult_Continue; + return true; } /** * @brief Called after a line has been preprocessed, if it has text. * * @param line Contents of line. - * @param lineno The line number it occurs on. * @param curtok Pointer to optionally store failed position in string. - * @return SMCResult directive. + * + * @return True to keep parsing, false otherwise. */ - virtual SMCResult ReadINI_RawLine(const char *line, unsigned int lineno, unsigned int *curtok) + virtual bool ReadINI_RawLine(const char *line, unsigned int *curtok) { - return SMCResult_Continue; + return true; } }; @@ -227,6 +177,35 @@ namespace SourceMod * // * / * */ + /** + * @brief Lists actions to take when an SMC parse hook is done. + */ + enum SMCResult + { + SMCResult_Continue, /**< Continue parsing */ + SMCResult_Halt, /**< Stop parsing here */ + SMCResult_HaltFail /**< Stop parsing and return SMCError_Custom */ + }; + + /** + * @brief Lists error codes possible from parsing an SMC file. + */ + enum SMCError + { + SMCError_Okay = 0, /**< No error */ + SMCError_StreamOpen, /**< Stream failed to open */ + SMCError_StreamError, /**< The stream died... somehow */ + SMCError_Custom, /**< A custom handler threw an error */ + SMCError_InvalidSection1, /**< A section was declared without quotes, and had extra tokens */ + SMCError_InvalidSection2, /**< A section was declared without any header */ + SMCError_InvalidSection3, /**< A section ending was declared with too many unknown tokens */ + SMCError_InvalidSection4, /**< A section ending has no matching beginning */ + SMCError_InvalidSection5, /**< A section beginning has no matching ending */ + SMCError_InvalidTokens, /**< There were too many unidentifiable strings on one line */ + SMCError_TokenOverflow, /**< The token buffer overflowed */ + SMCError_InvalidProperty1, /**< A property was declared outside of any section */ + }; + /** * @brief States for line/column */ @@ -350,9 +329,9 @@ namespace SourceMod * @param ini_listener Event handler for reading file. * @param line If non-NULL, will contain last line parsed (0 if file could not be opened). * @param col If non-NULL, will contain last column parsed (undefined if file could not be opened). - * @return An SMCError result code. + * @return True if parsing succeeded, false if file couldn't be opened or there was a syntax error. */ - virtual SMCError ParseFile_INI(const char *file, + virtual bool ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col) =0; From 7317f1d233d6f0cea32f403b5a1d8e3ba711960c Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sun, 3 Aug 2014 23:49:53 +0200 Subject: [PATCH 04/14] Update PackageScript. --- support/PackageScript | 2 ++ 1 file changed, 2 insertions(+) diff --git a/support/PackageScript b/support/PackageScript index 5185c646..8fa1e99f 100644 --- a/support/PackageScript +++ b/support/PackageScript @@ -292,6 +292,8 @@ scripting_files = [ 'include/newmenus.inc', 'include/sorting.inc', 'include/sqlx.inc', + 'include/textparse_ini.inc', + 'include/textparse_smc.inc', 'include/time.inc', 'include/vector.inc', ] From 287cc1a0e8e6a88dbdfeb36c9e6bbd59f5b0d312 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sun, 3 Aug 2014 23:54:48 +0200 Subject: [PATCH 05/14] Update MakeFile. --- amxmodx/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/amxmodx/Makefile b/amxmodx/Makefile index fc136574..10aae948 100755 --- a/amxmodx/Makefile +++ b/amxmodx/Makefile @@ -21,7 +21,8 @@ OBJECTS = meta_api.cpp CFile.cpp CVault.cpp vault.cpp float.cpp file.cpp modules CMenu.cpp util.cpp amx.cpp amxdbg.cpp natives.cpp newmenus.cpp debugger.cpp \ optimizer.cpp format.cpp messages.cpp libraries.cpp vector.cpp sorting.cpp \ nongpl_matches.cpp CFlagManager.cpp datastructs.cpp \ - trie_natives.cpp CDataPack.cpp datapacks.cpp stackstructs.cpp + trie_natives.cpp CDataPack.cpp datapacks.cpp stackstructs.cpp \ + CTextParsers.cpp textparse.cpp \ ############################################## ### CONFIGURE ANY OTHER FLAGS/OPTIONS HERE ### From b97093a778dc5cf1a17bd7e22ed88e3699246cec Mon Sep 17 00:00:00 2001 From: Arkshine Date: Mon, 4 Aug 2014 00:03:16 +0200 Subject: [PATCH 06/14] Add more debug in plugin. --- plugins/testsuite/textparse_test.sma | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/testsuite/textparse_test.sma b/plugins/testsuite/textparse_test.sma index 0bb37d28..92f54912 100644 --- a/plugins/testsuite/textparse_test.sma +++ b/plugins/testsuite/textparse_test.sma @@ -205,7 +205,7 @@ public ReadCSDM_ParseStart(INIParser:handle) public ReadCSDM_NewSection(INIParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) { - Debug && server_print("^tReadCSDM_NewSection - [%s]", section); + Debug && server_print("^tReadCSDM_NewSection - [%s] (invalid_tokens: '%s', close_bracked: '%s', extra_tokens: '%s')", section, invalid_tokens ? "yes" : "no", close_bracket ? "yes" : "no", extra_tokens ? "yes" : "no"); if (TrieKeyExists(ExpectedKVData, section)) { From 2d07189e38b3225f1112bc88a244882b2fa61124 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Mon, 4 Aug 2014 01:23:33 +0200 Subject: [PATCH 07/14] Fix few issues. --- amxmodx/textparse.h | 6 +++--- plugins/testsuite/textparse_test.sma | 15 ++++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/amxmodx/textparse.h b/amxmodx/textparse.h index 93d3ffc9..0d844f30 100644 --- a/amxmodx/textparse.h +++ b/amxmodx/textparse.h @@ -101,7 +101,7 @@ public: bool ReadINI_NewSection(const char *section, bool invalid_tokens, bool close_bracket, bool extra_tokens, unsigned int *curtok) { if (new_section != -1) - return executeForwards(new_section, handle, section, invalid_tokens, close_bracket, extra_tokens, *curtok) > 0 ? false : true; + return executeForwards(new_section, handle, section, invalid_tokens, close_bracket, extra_tokens, *curtok) > 0 ? true : false; return true; } @@ -109,7 +109,7 @@ public: bool ReadINI_KeyValue(const char *key, const char *value, bool invalid_tokens, bool equal_token, bool quotes, unsigned int *curtok) { if (key_value != -1) - return executeForwards(key_value, handle, key, value, invalid_tokens, equal_token, quotes, *curtok) > 0 ? false : true; + return executeForwards(key_value, handle, key, value, invalid_tokens, equal_token, quotes, *curtok) > 0 ? true : false; return true; } @@ -117,7 +117,7 @@ public: bool ReadINI_RawLine(const char *line, unsigned int *curtok) { if (raw_line != -1) - return executeForwards(raw_line, handle, line, *curtok) > 0 ? false : true; + return executeForwards(raw_line, handle, line, *curtok) > 0 ? true : false; return true; } diff --git a/plugins/testsuite/textparse_test.sma b/plugins/testsuite/textparse_test.sma index 92f54912..64e6ce82 100644 --- a/plugins/testsuite/textparse_test.sma +++ b/plugins/testsuite/textparse_test.sma @@ -212,33 +212,38 @@ public ReadCSDM_NewSection(INIParser:handle, const section[], bool:invalid_token if ((equal(section, "secondary") && !extra_tokens) || (equal(section, "botsecondary") && close_bracket)) { - return; + return true; } ++SuccessCount; } + + return true; } -public ReadCSDM_KeyValue(INIParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) +public bool:ReadCSDM_KeyValue(INIParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) { Debug && server_print("^t^tReadCSDM_KeyValue - %-32s %s", key, value); new buffer[128]; if (TrieGetString(ExpectedKVData, key, buffer, charsmax(buffer)) && equal(value, buffer)) { - if (equal(value, "colors") && !quotes) + if (equal(key, "colors") && !quotes) { - return; + return true; } ++SuccessCount; } + + return true; } -public ReadCSDM_CurrentLine(INIParser:handle, const line[], curtok) +public bool:ReadCSDM_CurrentLine(INIParser:handle, const line[], curtok) { //Debug && server_print("^t^tReadCSDM_CurrentLine - %s", line); ++SuccessCount; + return true; } public ReadCSDM_ParseEnd(INIParser:handle, bool:halted, bool:failed) From 4b652198f080577c9fdc0ef0bdfc4e4103655310 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Mon, 4 Aug 2014 01:42:19 +0200 Subject: [PATCH 08/14] Fix compilation. --- amxmodx/CTextParsers.cpp | 3 ++- amxmodx/textparse.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp index 4e79cc87..5234fed8 100644 --- a/amxmodx/CTextParsers.cpp +++ b/amxmodx/CTextParsers.cpp @@ -14,12 +14,13 @@ /* #include #include -#include #include #include #include #include */ +#include + TextParsers g_TextParser; ITextParsers *textparsers = &g_TextParser; diff --git a/amxmodx/textparse.cpp b/amxmodx/textparse.cpp index e72efed0..f272b6ac 100644 --- a/amxmodx/textparse.cpp +++ b/amxmodx/textparse.cpp @@ -238,7 +238,7 @@ static cell AMX_NATIVE_CALL INI_ParseFile(AMX *amx, cell *params) int length; const char *file = build_pathname("%s", get_amxstring(amx, params[2], 0, length)); - size_t line, col; + unsigned int line, col; bool result = textparsers->ParseFile_INI(file, p, &line, &col); *get_amxaddr(amx, params[3]) = line; From 3369f6d115e9a3d1bbe2fbf4fdd1e9d247a53202 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Wed, 6 Aug 2014 09:24:09 +0200 Subject: [PATCH 09/14] Update license in pawn includes and add them in project files. --- amxmodx/msvc10/amxmodx_mm.vcxproj | 2 ++ amxmodx/msvc10/amxmodx_mm.vcxproj.filters | 16 +++++++++++----- plugins/include/textparse_ini.inc | 12 ++++++++++++ plugins/include/textparse_smc.inc | 12 ++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index 1d8eec87..43b7cb13 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -405,6 +405,8 @@ + + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index bff2487d..3b1d4395 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -173,14 +173,14 @@ Source Files - + Source Files - + Source Files - + Source Files @@ -325,10 +325,10 @@ Header Files - + Header Files - + @@ -417,6 +417,12 @@ Pawn Includes + + Pawn Includes + + + Pawn Includes + diff --git a/plugins/include/textparse_ini.inc b/plugins/include/textparse_ini.inc index f5891a1c..699f6537 100644 --- a/plugins/include/textparse_ini.inc +++ b/plugins/include/textparse_ini.inc @@ -1,3 +1,15 @@ +// vim: set ts=4 sw=4 tw=99 noet: +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +// +// INI Parser Functions +// #if defined _textparse_ini_included #endinput diff --git a/plugins/include/textparse_smc.inc b/plugins/include/textparse_smc.inc index 3b76102d..9e8ea983 100644 --- a/plugins/include/textparse_smc.inc +++ b/plugins/include/textparse_smc.inc @@ -1,3 +1,15 @@ +// vim: set ts=4 sw=4 tw=99 noet: +// +// AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). +// Copyright (C) The AMX Mod X Development Team. +// +// This software is licensed under the GNU General Public License, version 3 or higher. +// Additional exceptions apply. For full license details, see LICENSE.txt or visit: +// https://alliedmods.net/amxmodx-license + +// +// SMC Parser Functions +// #if defined _textparse_smc_included #endinput From 8d2770f2f254260a0acc5b978ac87f077b6a733c Mon Sep 17 00:00:00 2001 From: Arkshine Date: Thu, 7 Aug 2014 00:43:29 +0200 Subject: [PATCH 10/14] Put back original SM headers. --- amxmodx/CTextParsers.cpp | 41 +++++++++++++++++++++++++++++----------- amxmodx/CTextParsers.h | 40 +++++++++++++++++++++++++++++---------- public/ITextParsers.h | 34 ++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 28 deletions(-) diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp index 5234fed8..5b2d0599 100644 --- a/amxmodx/CTextParsers.cpp +++ b/amxmodx/CTextParsers.cpp @@ -1,14 +1,33 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet: - * - * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). - * Copyright (C) The AMX Mod X Development Team. - * - * This software is licensed under the GNU General Public License, version 3 or higher. - * Additional exceptions apply. For full license details, see LICENSE.txt or visit: - * https://alliedmods.net/amxmodx-license - */ - +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ #include "amxmodx.h" #include "CTextParsers.h" /* diff --git a/amxmodx/CTextParsers.h b/amxmodx/CTextParsers.h index 8ceeca6d..45ebe52c 100644 --- a/amxmodx/CTextParsers.h +++ b/amxmodx/CTextParsers.h @@ -1,13 +1,33 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet: - * - * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). - * Copyright (C) The AMX Mod X Development Team. - * - * This software is licensed under the GNU General Public License, version 3 or higher. - * Additional exceptions apply. For full license details, see LICENSE.txt or visit: - * https://alliedmods.net/amxmodx-license - */ +/** +* vim: set ts=4 : +* ============================================================================= +* SourceMod +* Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. +* ============================================================================= +* +* This program is free software; you can redistribute it and/or modify it under +* the terms of the GNU General Public License, version 3.0, as published by the +* Free Software Foundation. +* +* 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 General Public License for more +* details. +* +* You should have received a copy of the GNU General Public License along with +* this program. If not, see . +* +* As a special exception, AlliedModders LLC gives you permission to link the +* code of this program (as well as its derivative works) to "Half-Life 2," the +* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +* by the Valve Corporation. You must obey the GNU General Public License in +* all respects for all other code used. Additionally, AlliedModders LLC grants +* this exception to all derivative works. AlliedModders LLC defines further +* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +* or . +* +* Version: $Id$ +*/ #ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ #define _INCLUDE_SOURCEMOD_TEXTPARSERS_H_ diff --git a/public/ITextParsers.h b/public/ITextParsers.h index 2d3dfc5e..0f88be9e 100644 --- a/public/ITextParsers.h +++ b/public/ITextParsers.h @@ -1,12 +1,32 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet: +/** + * vim: set ts=4 : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= * - * AMX Mod X, based on AMX Mod by Aleksander Naszko ("OLO"). - * Copyright (C) The AMX Mod X Development Team. + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * 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 General Public License for more + * details. * - * This software is licensed under the GNU General Public License, version 3 or higher. - * Additional exceptions apply. For full license details, see LICENSE.txt or visit: - * https://alliedmods.net/amxmodx-license + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ */ #ifndef _INCLUDE_SOURCEMOD_TEXTPARSERS_INTERFACE_H_ From 6da20780673f4edfc3b616f0cd6f0158711f2a68 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Thu, 7 Aug 2014 00:51:01 +0200 Subject: [PATCH 11/14] Replace snprintf by UTIL_Format. --- amxmodx/CTextParsers.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/amxmodx/CTextParsers.cpp b/amxmodx/CTextParsers.cpp index 5b2d0599..3d508899 100644 --- a/amxmodx/CTextParsers.cpp +++ b/amxmodx/CTextParsers.cpp @@ -137,7 +137,7 @@ SMCError TextParsers::ParseSMCFile(const char *file, states->col = 0; } /*libsys->GetPlatformError(error, sizeof(error));*/ - snprintf(buffer, maxsize, "File could not be opened: %s", error); + UTIL_Format(buffer, maxsize, "File could not be opened: %s", error); return SMCError_StreamOpen; } @@ -146,7 +146,7 @@ SMCError TextParsers::ParseSMCFile(const char *file, fclose(fp); errstr = GetSMCErrorString(result); - snprintf(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); + UTIL_Format(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); return result; } @@ -197,7 +197,7 @@ SMCError TextParsers::ParseSMCStream(const char *stream, result = ParseStream_SMC(&rs, RawStreamReader, smc_listener, states); const char *errstr = GetSMCErrorString(result); - snprintf(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); + UTIL_Format(buffer, maxsize, "%s", errstr != NULL ? errstr : "Unknown error"); return result; } From a0852adf1883e6562177f800358343479d2873f1 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Thu, 7 Aug 2014 00:53:15 +0200 Subject: [PATCH 12/14] Fix wrong log error message. --- amxmodx/textparse.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/amxmodx/textparse.cpp b/amxmodx/textparse.cpp index f272b6ac..d566221a 100644 --- a/amxmodx/textparse.cpp +++ b/amxmodx/textparse.cpp @@ -56,7 +56,7 @@ static cell AMX_NATIVE_CALL SMC_SetParseStart(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid SMC parse handle (%d)", params[1]); return 0; } @@ -84,7 +84,7 @@ static cell AMX_NATIVE_CALL SMC_SetParseEnd(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid SMC parse handle (%d)", params[1]); return 0; } @@ -112,7 +112,7 @@ static cell AMX_NATIVE_CALL SMC_SetReaders(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid SMC parse handle (%d)", params[1]); return 0; } @@ -150,7 +150,7 @@ static cell AMX_NATIVE_CALL SMC_SetRawLine(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid SMC parse handle (%d)", params[1]); return 0; } @@ -178,7 +178,7 @@ static cell AMX_NATIVE_CALL SMC_ParseFile(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid SMC parse handle (%d)", params[1]); return 0; } @@ -231,7 +231,7 @@ static cell AMX_NATIVE_CALL INI_ParseFile(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid INI parse handle (%d)", params[1]); return 0; } @@ -254,7 +254,7 @@ static cell AMX_NATIVE_CALL INI_SetParseStart(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid INI parse handle (%d)", params[1]); return 0; } @@ -282,7 +282,7 @@ static cell AMX_NATIVE_CALL INI_SetParseEnd(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid INI parse handle (%d)", params[1]); return 0; } @@ -310,7 +310,7 @@ static cell AMX_NATIVE_CALL INI_SetReaders(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid INI parse handle (%d)", params[1]); return 0; } @@ -343,7 +343,7 @@ static cell AMX_NATIVE_CALL INI_SetRawLine(AMX *amx, cell *params) if (p == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid INI parse handle (%d)", params[1]); return 0; } From 6b5387f4284fa51053b7cdef7892aada50703b18 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Thu, 7 Aug 2014 01:16:44 +0200 Subject: [PATCH 13/14] Fix documentation. --- plugins/include/textparse_ini.inc | 38 +++++++++++++------------- plugins/include/textparse_smc.inc | 44 +++++++++++++++---------------- 2 files changed, 40 insertions(+), 42 deletions(-) diff --git a/plugins/include/textparse_ini.inc b/plugins/include/textparse_ini.inc index 699f6537..70dd2711 100644 --- a/plugins/include/textparse_ini.inc +++ b/plugins/include/textparse_ini.inc @@ -17,11 +17,8 @@ #define _textparse_ini_included /** - * This parser API is entirely event based. You must hook events to receive data. - * The file format can ben either INI or SMC (also known as "SourceMod Configuration". - * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). - * Also please note INI format is handled differently. Because more "simple" to parse, some - * event doesn't exist and callback prototype can be different. + * This parser API is entirely event based. + * You must hook events to receive data. */ /** @@ -79,23 +76,23 @@ enum INIParser native INIParser:INI_CreateParser(); /** - * Disposes of a INI parser. + * Disposes of an INI parser. * - * @param handle Handle to a INI Parse. + * @param handle Handle to an INI Parse structure. * * @return True if disposed, false otherwise. */ native INI_DestroyParser(&INIParser:handle); /** - * Parses a INI config file. + * Parses an INI config file. * * @param handle A handle to an INI Parse structure. * @param file A string containing the file path. * @param line An optional by reference cell to store the last line number read. * @param col An optional by reference cell to store the last column number read. - * @return A SMCParseError result. + * @return An SMCParseError result. * @error Invalid or corrupt handle. */ native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); @@ -107,7 +104,7 @@ native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); * - * Called when parsing is started. * - * @param handle The INI Parse handle. + * @param handle A handle to an INI Parse structure. * * @noreturn * @@ -122,21 +119,21 @@ native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); native INI_SetParseStart(INIParser:handle, const func[]); /** - * Sets the INI_ParseEnd of a parse handle. + * Sets the INI_ParseEnd function of a parse handle. * * @note Below is the prototype of callback: * - * Called when parsing is halted. * - * @param handle The INI Parse handle. + * @param handle A handle to an INI Parse structure. * @param halted True if abnormally halted, false otherwise. * * @noreturn * * public OnParseEnd(INIParser:handle, bool:halted) * - - * @param handle Handle to a INI Parse. - * @param func A ParseEnd callback.. + * @param handle Handle to an INI Parse structure. + * @param func A ParseEnd callback. * * @noreturn * @error Invalid or corrupt handle. @@ -144,19 +141,20 @@ native INI_SetParseStart(INIParser:handle, const func[]); native INI_SetParseEnd(INIParser:handle, const func[]); /** - * Sets the three main reader functions. + * Sets the two main reader functions. * * @note Below is the prototype of callback: * - * NewSection: - * Called when the parser finds the end of the current section. + * Called when the parser finds a new section. * + * @param handle Handle to an INI Parse structure. * @param section Name of section in between the [ and ] characters. * @param invalid_tokens True if invalid tokens were detected in the name. * @param close_bracket True if a closing bracket was detected, false otherwise. * @param extra_tokens True if extra tokens were detected on the line. * @param curtok Contains current token in the line where the section name starts. - * can add to this offset when failing to point to a token. + * You can add to this offset when failing to point to a token. * @return True to keep parsing, false otherwise. * * public bool:OnNewSection(INIParser:handle, const section[], bool:invalid_tokens, bool:close_bracket, bool:extra_tokens, curtok) @@ -164,7 +162,7 @@ native INI_SetParseEnd(INIParser:handle, const func[]); * KeyValue: * Called when the parser finds a new key/value pair. * - * @param handle The INI Parse handle. + * @param handle Handle to an INI Parse structure. * @param key Name of key. * @param value String containing value (with quotes stripped, if any). * @param invalid_tokens Whether or not the key contained invalid tokens. @@ -176,7 +174,7 @@ native INI_SetParseEnd(INIParser:handle, const func[]); * * public bool:OnKeyValue(INIParser:handle, const key[], const value[], bool:invalid_tokens, bool:equal_token, bool:quotes, curtok) * - - * @param handle The INI parse handle. + * @param handle Handle to an INI Parse structure. * @param kv A KeyValue callback. * @param ns An optional NewSection callback. * @@ -200,7 +198,7 @@ native INI_SetReaders(INIParser:smc, const kvFunc[], const nsFunc[] = "" ); * * public bool:OnRawLine(INIParser:smc, const line[], lineno, curtok) * - * @param handle Handle to an INI Parse. + * @param handle Handle to an INI Parse structure. * @param func A RawLine callback. * * @noreturn diff --git a/plugins/include/textparse_smc.inc b/plugins/include/textparse_smc.inc index 9e8ea983..5e54af18 100644 --- a/plugins/include/textparse_smc.inc +++ b/plugins/include/textparse_smc.inc @@ -17,11 +17,9 @@ #define _textparse_smc_included /** - * This parser API is entirely event based. You must hook events to receive data. - * The file format can ben either INI or SMC (also known as "SourceMod Configuration". - * SMC format is nearly identical to VDF (also known as "Valve's KeyValues format"). - * Also please note INI format is handled differently. Because more "simple" to parse, some - * event doesn't exist and callback prototype can be different. + * Everything below describes the SMC Parse, or "SourceMod Configuration" format. + * This parser is entirely event based. You must hook events to receive data. + * The file format itself is nearly identical to Valve's KeyValues format (also known as VDF). */ /** @@ -109,9 +107,9 @@ enum SMCError native SMCParser:SMC_CreateParser(); /** - * Disposes of a SMC parser. + * Disposes of an SMC parser. * - * @param handle Handle to a SMC Parse + * @param handle Handle to an SMC Parse structure. * * @return True if disposed, false otherwise. */ @@ -125,7 +123,7 @@ native SMC_DestroyParser(&SMCParser:handle); * @param line An optional by reference cell to store the last line number read. * @param col An optional by reference cell to store the last column number read. * - * @return A SMCParseError result. + * @return An SMCParseError result. * @error Invalid or corrupt handle. */ native SMCError:SMC_ParseFile(SMCParser:handle, const file[], &line = 0, &col = 0); @@ -137,13 +135,13 @@ native SMCError:SMC_ParseFile(SMCParser:handle, const file[], &line = 0, &col = * - * Called when parsing is started. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. * * @noreturn * * public OnParseStart(SMCParser:handle) * - - * @param handle Handle to a Text Parse. + * @param handle Handle to an SMC Parse structure. * @param func A ParseStart callback. * * @noreturn @@ -152,13 +150,13 @@ native SMCError:SMC_ParseFile(SMCParser:handle, const file[], &line = 0, &col = native SMC_SetParseStart(SMCParser:handle, const func[]); /** - * Sets the SMC_ParseEnd of a parse handle. + * Sets the SMC_ParseEnd function of a parse handle. * * @note Below is the prototype of callback: * - * Called when parsing is halted. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. * @param halted True if abnormally halted, false otherwise. * @param failed True if parsing failed, false otherwise. * @@ -166,8 +164,8 @@ native SMC_SetParseStart(SMCParser:handle, const func[]); * * public OnParseEnd(SMCParser:handle, bool:halted, bool:failed) * - - * @param handle Handle to a Text Parse. - * @param func A ParseEnd callback.. + * @param handle Handle to an SMC Parse structure. + * @param func A ParseEnd callback. * * @noreturn * @error Invalid or corrupt handle. @@ -181,9 +179,9 @@ native SMC_SetParseEnd(SMCParser:handle, const func[]); * @note Below is the prototype of callbacks: * - * NewSection: - * Called when the parser finds the end of the current section. + * Called when the parser finds a new section or sub-section. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. * @param name String containing section name. * * @return An SMCResult action to take. @@ -193,22 +191,24 @@ native SMC_SetParseEnd(SMCParser:handle, const func[]); * KeyValue: * Called when the parser finds a new key/value pair. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. * @param key String containing key name. * @param value String containing value name. * - * @return A SMCResult action to take. + * @return An SMCResult action to take. * * public SMCResult:OnKeyValue(SMCParser:handle, const key[], const value[]) * * EndSection: * Called when the parser finds the end of the current section. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. + * + * @return An SMCResult action to take. * * public SMCResult:OnEndSection(SMCParser:handle) * - - * @param handle The Text parse handle. + * @param handle Handle to an SMC Parse structure. * @param kv A KeyValue callback. * @param ns An optional NewSection callback. * @param es An optional EndSection callback. @@ -224,7 +224,7 @@ native SMC_SetReaders(SMCParser:smc, const kvFunc[], const nsFunc[] = "", const * - * Called whenever a raw line is read. * - * @param handle The Text Parse handle. + * @param handle Handle to an SMC Parse structure. * @param line A string containing the raw line from the file. * @param lineno The line number it occurs on. * @@ -232,7 +232,7 @@ native SMC_SetReaders(SMCParser:smc, const kvFunc[], const nsFunc[] = "", const * * public SMCResult:SMC_RawLine(SMCParser:handle, const line[], lineno) * - - * @param handle Handle to an Text Parse. + * @param handle Handle to an SMC Parse structure. * @param func A RawLine callback. * * @noreturn From 29eb4e4647e16ea40df2e0321cfada96ff52ce1b Mon Sep 17 00:00:00 2001 From: Arkshine Date: Thu, 7 Aug 2014 01:18:45 +0200 Subject: [PATCH 14/14] Fix documentation. --- plugins/include/textparse_ini.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/include/textparse_ini.inc b/plugins/include/textparse_ini.inc index 70dd2711..eb96b532 100644 --- a/plugins/include/textparse_ini.inc +++ b/plugins/include/textparse_ini.inc @@ -110,7 +110,7 @@ native bool:INI_ParseFile(INIParser:handle, const file[], &line = 0, &col = 0); * * public OnParseStart(INIParser:handle) * - - * @param handle Handle to a INI Parse. + * @param handle Handle to an INI Parse structure. * @param func A ParseStart callback. * * @noreturn