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',