committed shiny new SMC parser (really Valve XML or whatever)
--HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40202
This commit is contained in:
parent
d7c3c577ed
commit
89c75b1940
@ -3,6 +3,7 @@
|
|||||||
#include <wctype.h>
|
#include <wctype.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
#include "CTextParsers.h"
|
#include "CTextParsers.h"
|
||||||
|
|
||||||
CTextParsers g_TextParse;
|
CTextParsers g_TextParse;
|
||||||
@ -33,13 +34,572 @@ unsigned int CTextParsers::GetUTF8CharBytes(const char *stream)
|
|||||||
return _GetUTF8CharBytes(stream);
|
return _GetUTF8CharBytes(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CTextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc_listener, unsigned int *line, unsigned int *col, bool strict)
|
/**
|
||||||
{
|
* Character streams
|
||||||
/* This beast is a lot more rigorous than our INI friend. */
|
*/
|
||||||
|
|
||||||
return false;
|
struct CharStream
|
||||||
|
{
|
||||||
|
const char *curpos;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool CharStreamReader(void *stream, char *buffer, size_t maxlength, unsigned int *read)
|
||||||
|
{
|
||||||
|
CharStream *srdr = (CharStream *)stream;
|
||||||
|
|
||||||
|
const char *ptr = srdr->curpos;
|
||||||
|
for (size_t i=0; i<maxlength; i++)
|
||||||
|
{
|
||||||
|
if (*ptr == '\0')
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*buffer++ = *ptr++;
|
||||||
|
}
|
||||||
|
|
||||||
|
*read = ptr - srdr->curpos;
|
||||||
|
|
||||||
|
srdr->curpos = ptr;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SMCParseError CTextParsers::ParseString_SMC(const char *stream,
|
||||||
|
ITextListener_SMC *smc,
|
||||||
|
unsigned int *line,
|
||||||
|
unsigned int *col)
|
||||||
|
{
|
||||||
|
CharStream srdr = { stream };
|
||||||
|
|
||||||
|
return ParseStream_SMC(&srdr, CharStreamReader, smc, line, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<unsigned int>(num);
|
||||||
|
|
||||||
|
if (num == 0 && feof((FILE *)stream))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ferror((FILE *)stream) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
SMCParseError CTextParsers::ParseFile_SMC(const char *file, ITextListener_SMC *smc, unsigned int *line, unsigned int *col)
|
||||||
|
{
|
||||||
|
FILE *fp = fopen(file, "rt");
|
||||||
|
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
return SMCParse_StreamOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
SMCParseError result = ParseStream_SMC(fp, FileStreamReader, smc, line, col);
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw parsing of streams with helper functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct StringInfo
|
||||||
|
{
|
||||||
|
StringInfo() : quoted(false), ptr(NULL), end(NULL), special(false) { }
|
||||||
|
char *ptr;
|
||||||
|
char *end;
|
||||||
|
bool special;
|
||||||
|
bool quoted;
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if (data.special)
|
||||||
|
{
|
||||||
|
//:TODO: this string has special tokens in it, like \, and we must
|
||||||
|
//resolve these before passing the string back to the app
|
||||||
|
}
|
||||||
|
|
||||||
|
*(data.end) = '\0';
|
||||||
|
|
||||||
|
return data.ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *rotate(StringInfo info[3])
|
||||||
|
{
|
||||||
|
if (info[2].ptr != NULL)
|
||||||
|
{
|
||||||
|
return info[2].ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info[0].ptr != NULL)
|
||||||
|
{
|
||||||
|
info[2] = info[1];
|
||||||
|
info[1] = info[0];
|
||||||
|
info[0] = StringInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void scrap(StringInfo info[3])
|
||||||
|
{
|
||||||
|
info[2] = StringInfo();
|
||||||
|
info[1] = StringInfo();
|
||||||
|
info[0] = StringInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reloc(StringInfo &data, unsigned int bytes)
|
||||||
|
{
|
||||||
|
if (data.ptr)
|
||||||
|
{
|
||||||
|
data.ptr -= bytes;
|
||||||
|
}
|
||||||
|
if (data.end)
|
||||||
|
{
|
||||||
|
data.end -= bytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *lowstring(StringInfo info[3])
|
||||||
|
{
|
||||||
|
for (int i=2; i>=0; i--)
|
||||||
|
{
|
||||||
|
if (info[i].ptr)
|
||||||
|
{
|
||||||
|
return info[i].ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
SMCParseError CTextParsers::ParseStream_SMC(void *stream,
|
||||||
|
STREAMREADER srdr,
|
||||||
|
ITextListener_SMC *smc,
|
||||||
|
unsigned int *line,
|
||||||
|
unsigned int *col)
|
||||||
|
{
|
||||||
|
char in_buf[4096];
|
||||||
|
char *reparse_point = NULL;
|
||||||
|
char *parse_point = in_buf;
|
||||||
|
char *line_begin = in_buf;
|
||||||
|
unsigned int read;
|
||||||
|
unsigned int curline = 1;
|
||||||
|
unsigned int curtok = 0;
|
||||||
|
unsigned int curlevel = 0;
|
||||||
|
bool in_quote = false;
|
||||||
|
bool ignoring = false;
|
||||||
|
bool eol_comment = false;
|
||||||
|
bool ml_comment = false;
|
||||||
|
unsigned int i;
|
||||||
|
SMCParseError err = SMCParse_Okay;
|
||||||
|
SMCParseResult res;
|
||||||
|
char c;
|
||||||
|
|
||||||
|
StringInfo strings[3];
|
||||||
|
StringInfo emptystring;
|
||||||
|
|
||||||
|
smc->ReadSMC_ParseStart();
|
||||||
|
|
||||||
|
while (srdr(stream, parse_point, sizeof(in_buf) - (parse_point - line_begin) - 1, &read))
|
||||||
|
{
|
||||||
|
if (!read)
|
||||||
|
{
|
||||||
|
err = SMCParse_StreamError;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* :TODO: do this outside of the main loop somehow
|
||||||
|
* This checks for BOM markings
|
||||||
|
*/
|
||||||
|
if (curline == 1 &&
|
||||||
|
in_buf[0] == (char)0xEF &&
|
||||||
|
in_buf[1] == (char)0xBB &&
|
||||||
|
in_buf[2] == (char)0xBF)
|
||||||
|
{
|
||||||
|
parse_point = &in_buf[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reparse_point)
|
||||||
|
{
|
||||||
|
read += (parse_point - reparse_point);
|
||||||
|
parse_point = reparse_point;
|
||||||
|
reparse_point = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; i<read; i++)
|
||||||
|
{
|
||||||
|
c = parse_point[i];
|
||||||
|
if (c == '\n')
|
||||||
|
{
|
||||||
|
/* If we got a newline, there's a lot of things that could have happened in the interim.
|
||||||
|
* First, let's make sure the staged strings are rotated.
|
||||||
|
*/
|
||||||
|
if (strings[0].ptr)
|
||||||
|
{
|
||||||
|
strings[0].end = &parse_point[i];
|
||||||
|
if (rotate(strings) != NULL)
|
||||||
|
{
|
||||||
|
err = SMCParse_InvalidTokens;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Next, let's clear some line-based values that may no longer have meaning */
|
||||||
|
eol_comment = false;
|
||||||
|
in_quote = false;
|
||||||
|
if (ignoring && !ml_comment)
|
||||||
|
{
|
||||||
|
ignoring = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pass the raw line onto the listener */
|
||||||
|
if ((res=smc->ReadSMC_RawLine(line_begin, curline)) != SMCParse_Continue)
|
||||||
|
{
|
||||||
|
err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now we check the sanity of our staged strings! */
|
||||||
|
if (strings[2].ptr)
|
||||||
|
{
|
||||||
|
if (!curlevel)
|
||||||
|
{
|
||||||
|
err = SMCParse_InvalidProperty1;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
/* Assume the next string is a property and pass the info on. */
|
||||||
|
if ((res=smc->ReadSMC_KeyValue(
|
||||||
|
FixupString(strings[2]),
|
||||||
|
FixupString(strings[1]),
|
||||||
|
strings[2].quoted,
|
||||||
|
strings[1].quoted)) != SMCParse_Continue)
|
||||||
|
{
|
||||||
|
err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
scrap(strings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change the states for the next line */
|
||||||
|
curtok = 0;
|
||||||
|
curline++;
|
||||||
|
line_begin = &parse_point[i+1]; //Note: safe because this gets relocated later
|
||||||
|
} else if (ignoring) {
|
||||||
|
if (in_quote)
|
||||||
|
{
|
||||||
|
/* If i was 0, this case is impossible due to reparsing */
|
||||||
|
if ((i != 0) && 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 = SMCParse_InvalidTokens;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
} else if (c == '\\' && i == (read - 1)) {
|
||||||
|
strings[0].special = true;
|
||||||
|
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++;
|
||||||
|
curtok++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* Check if we're whitespace or not */
|
||||||
|
if (!g_ws_chartable[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 = &reparse_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 = SMCParse_InvalidSection1;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Sections must always be alone */
|
||||||
|
if (strings[2].ptr != NULL)
|
||||||
|
{
|
||||||
|
err = SMCParse_InvalidSection1;
|
||||||
|
goto failed;
|
||||||
|
} else if (strings[1].ptr == NULL) {
|
||||||
|
err = SMCParse_InvalidSection2;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if ((res=smc->ReadSMC_NewSection(FixupString(strings[1]), strings[1].quoted))
|
||||||
|
!= SMCParse_Continue)
|
||||||
|
{
|
||||||
|
err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_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 = SMCParse_InvalidSection3;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if (strings[2].ptr)
|
||||||
|
{
|
||||||
|
if (!curlevel)
|
||||||
|
{
|
||||||
|
err = SMCParse_InvalidProperty1;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
if ((res=smc->ReadSMC_KeyValue(
|
||||||
|
FixupString(strings[2]),
|
||||||
|
FixupString(strings[1]),
|
||||||
|
strings[2].quoted,
|
||||||
|
strings[1].quoted))
|
||||||
|
!= SMCParse_Continue)
|
||||||
|
{
|
||||||
|
err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_Okay;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
} else if (strings[1].ptr) {
|
||||||
|
err = SMCParse_InvalidSection3;
|
||||||
|
goto failed;
|
||||||
|
} else if (!curlevel) {
|
||||||
|
err = SMCParse_InvalidSection4;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
/* Now it's safe to leave the section */
|
||||||
|
scrap(strings);
|
||||||
|
if ((res=smc->ReadSMC_LeavingSection()) != SMCParse_Continue)
|
||||||
|
{
|
||||||
|
err = (res == SMCParse_HaltFail) ? SMCParse_Custom : SMCParse_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 = SMCParse_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 = SMCParse_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 = SMCParse_InvalidTokens;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance which token we're on */
|
||||||
|
curtok++;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = SMCParse_TokenOverflow;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we're done parsing and there are tokens left over... */
|
||||||
|
if (curlevel)
|
||||||
|
{
|
||||||
|
err = SMCParse_InvalidSection5;
|
||||||
|
goto failed;
|
||||||
|
} else if (strings[0].ptr || strings[1].ptr) {
|
||||||
|
err = SMCParse_InvalidTokens;
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
smc->ReadSMC_ParseEnd(false, false);
|
||||||
|
|
||||||
|
return SMCParse_Okay;
|
||||||
|
|
||||||
|
failed:
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
*line = curline;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (col)
|
||||||
|
{
|
||||||
|
*col = curtok;
|
||||||
|
}
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INI parser
|
||||||
|
*/
|
||||||
|
|
||||||
bool CTextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
|
bool CTextParsers::ParseFile_INI(const char *file, ITextListener_INI *ini_listener, unsigned int *line, unsigned int *col)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(file, "rt");
|
FILE *fp = fopen(file, "rt");
|
||||||
|
@ -23,6 +23,15 @@ inline unsigned int _GetUTF8CharBytes(const char *stream)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 CTextParsers : public ITextParsers
|
class CTextParsers : public ITextParsers
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -33,13 +42,23 @@ public:
|
|||||||
unsigned int *line,
|
unsigned int *line,
|
||||||
unsigned int *col);
|
unsigned int *col);
|
||||||
|
|
||||||
virtual bool ParseFile_SMC(const char *file,
|
virtual SMCParseError ParseFile_SMC(const char *file,
|
||||||
ITextListener_SMC *smc_listener,
|
ITextListener_SMC *smc_listener,
|
||||||
unsigned int *line,
|
unsigned int *line,
|
||||||
unsigned int *col,
|
unsigned int *col);
|
||||||
bool strict);
|
|
||||||
|
|
||||||
virtual unsigned int GetUTF8CharBytes(const char *stream);
|
virtual unsigned int GetUTF8CharBytes(const char *stream);
|
||||||
|
private:
|
||||||
|
SMCParseError ParseString_SMC(const char *stream,
|
||||||
|
ITextListener_SMC *smc,
|
||||||
|
unsigned int *line,
|
||||||
|
unsigned int *col);
|
||||||
|
SMCParseError ParseStream_SMC(void *stream,
|
||||||
|
STREAMREADER srdr,
|
||||||
|
ITextListener_SMC *smc,
|
||||||
|
unsigned int *line,
|
||||||
|
unsigned int *col);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
extern CTextParsers g_TextParse;
|
extern CTextParsers g_TextParse;
|
||||||
|
@ -102,17 +102,18 @@ namespace SourceMod
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* :TODO: write this in CFG format so it makes sense
|
* :TODO: write this in CFG (context free grammar) format so it makes sense
|
||||||
*
|
*
|
||||||
* The SMC file format is defined as:
|
* The SMC file format is defined as:
|
||||||
* WHITESPACE: 0x20, \n, \t, \r
|
* WHITESPACE: 0x20, \n, \t, \r
|
||||||
* IDENTIFIER: Any ASCII character EXCLUDING ", ', :, WHITESPACE
|
* IDENTIFIER: Any ASCII character EXCLUDING ", {, }, ;, //, /*, or WHITESPACE.
|
||||||
* STRING: Any set of symbols
|
* 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.
|
* Basic syntax is comprised of SECTIONBLOCKs.
|
||||||
* A SECTIONBLOCK defined as:
|
* A SECTIONBLOCK defined as:
|
||||||
*
|
*
|
||||||
* SECTION: "SECTIONNAME"
|
* SECTIONNAME
|
||||||
* {
|
* {
|
||||||
* OPTION
|
* OPTION
|
||||||
* }
|
* }
|
||||||
@ -121,11 +122,14 @@ namespace SourceMod
|
|||||||
* A new line will terminate an OPTION, but there can be more than one OPTION per line.
|
* A new line will terminate an OPTION, but there can be more than one OPTION per line.
|
||||||
* OPTION is defined any of:
|
* OPTION is defined any of:
|
||||||
* "KEY" "VALUE"
|
* "KEY" "VALUE"
|
||||||
* "SINGLEKEY"
|
|
||||||
* SECTIONBLOCK
|
* SECTIONBLOCK
|
||||||
*
|
*
|
||||||
* SECTION is an IDENTIFIER
|
|
||||||
* SECTIONNAME, KEY, VALUE, and SINGLEKEY are strings
|
* 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
|
* For an example, see configs/permissions.cfg
|
||||||
*
|
*
|
||||||
@ -135,17 +139,33 @@ namespace SourceMod
|
|||||||
* ;<TEXT>
|
* ;<TEXT>
|
||||||
* //<TEXT>
|
* //<TEXT>
|
||||||
* /*<TEXT> */
|
* /*<TEXT> */
|
||||||
|
|
||||||
|
enum SMCParseResult
|
||||||
|
{
|
||||||
|
SMCParse_Continue, //continue parsing
|
||||||
|
SMCParse_Halt, //stop parsing here
|
||||||
|
SMCParse_HaltFail //stop parsing and return failure
|
||||||
|
};
|
||||||
|
|
||||||
|
enum SMCParseError
|
||||||
|
{
|
||||||
|
SMCParse_Okay, //no error
|
||||||
|
SMCParse_StreamOpen, //stream failed to open
|
||||||
|
SMCParse_StreamError, //the stream died... somehow
|
||||||
|
SMCParse_Custom, //a custom handler threw an error
|
||||||
|
SMCParse_InvalidSection1, //a section was declared without quotes, and had extra tokens
|
||||||
|
SMCParse_InvalidSection2, //a section was declared without any header
|
||||||
|
SMCParse_InvalidSection3, //a section ending was declared with too many unknown tokens
|
||||||
|
SMCParse_InvalidSection4, //a section ending has no matching beginning
|
||||||
|
SMCParse_InvalidSection5, //a section beginning has no matching ending
|
||||||
|
SMCParse_InvalidTokens, //there were too many unidentifiable strings on one line
|
||||||
|
SMCParse_TokenOverflow, //the token buffer overflowed
|
||||||
|
SMCParse_InvalidProperty1, //a property was declared outside of any section
|
||||||
|
};
|
||||||
|
|
||||||
class ITextListener_SMC
|
class ITextListener_SMC
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
enum SMCParseResult
|
|
||||||
{
|
|
||||||
SMCParse_Continue, //continue parsing
|
|
||||||
SMCParse_SkipSection, //skip the rest of the current section
|
|
||||||
SMCParse_Halt, //stop parsing here
|
|
||||||
SMCParse_HaltFail //stop parsing and return failure
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Called when starting parsing.
|
* @brief Called when starting parsing.
|
||||||
*/
|
*/
|
||||||
@ -163,17 +183,25 @@ namespace SourceMod
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Called when a warning occurs.
|
||||||
|
* @param error By-reference variable containing the error message of the warning.
|
||||||
|
* @param tokens Pointer to the token stream causing the error.
|
||||||
|
* @return SMCParseResult directive.
|
||||||
|
*/
|
||||||
|
virtual SMCParseResult ReadSMC_OnWarning(SMCParseError &error, const char *tokens)
|
||||||
|
{
|
||||||
|
return SMCParse_HaltFail;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Called when entering a new section
|
* @brief Called when entering a new section
|
||||||
*
|
*
|
||||||
* @param name Name of section, with the colon omitted.
|
* @param name Name of section, with the colon omitted.
|
||||||
* @param option Optional text after the colon, quotes removed. NULL if none.
|
* @param opt_quotes Whether or not the option string was enclosed in quotes.
|
||||||
* @param colon Whether or not the required ':' was encountered.
|
|
||||||
* @return SMCParseResult directive.
|
* @return SMCParseResult directive.
|
||||||
*/
|
*/
|
||||||
virtual SMCParseResult ReadSMC_NewSection(const char *name,
|
virtual SMCParseResult ReadSMC_NewSection(const char *name, bool opt_quotes)
|
||||||
const char *option,
|
|
||||||
bool colon)
|
|
||||||
{
|
{
|
||||||
return SMCParse_Continue;
|
return SMCParse_Continue;
|
||||||
}
|
}
|
||||||
@ -198,7 +226,6 @@ namespace SourceMod
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Called when leaving the current section.
|
* @brief Called when leaving the current section.
|
||||||
* Note: Skipping the section has no meaning here.
|
|
||||||
*
|
*
|
||||||
* @return SMCParseResult directive.
|
* @return SMCParseResult directive.
|
||||||
*/
|
*/
|
||||||
@ -259,14 +286,12 @@ namespace SourceMod
|
|||||||
* @param smc_listener Event handler for reading file.
|
* @param smc_listener Event handler for reading file.
|
||||||
* @param line If non-NULL, will contain last line parsed (0 if file could not be opened).
|
* @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).
|
* @param col If non-NULL, will contain last column parsed (undefined if file could not be opened).
|
||||||
* @param strict If strict mode is enabled, the parsing rules are obeyed rigorously rather than loosely.
|
* @return An SMCParseError result code.
|
||||||
* @return True if parsing succeded, false if file couldn't be opened or there was a syntax error.
|
|
||||||
*/
|
*/
|
||||||
virtual bool ParseFile_SMC(const char *file,
|
virtual SMCParseError ParseFile_SMC(const char *file,
|
||||||
ITextListener_SMC *smc_listener,
|
ITextListener_SMC *smc_listener,
|
||||||
unsigned int *line,
|
unsigned int *line,
|
||||||
unsigned int *col,
|
unsigned int *col) =0;
|
||||||
bool strict) =0;
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Returns the number of bytes that a multi-byte character contains in a UTF-8 stream.
|
* @brief Returns the number of bytes that a multi-byte character contains in a UTF-8 stream.
|
||||||
|
@ -3,14 +3,25 @@
|
|||||||
#include "sm_version.h"
|
#include "sm_version.h"
|
||||||
#include "sourcemod.h"
|
#include "sourcemod.h"
|
||||||
|
|
||||||
|
#include "CTextParsers.h"
|
||||||
|
|
||||||
SourceMod_Core g_SourceMod_Core;
|
SourceMod_Core g_SourceMod_Core;
|
||||||
|
|
||||||
PLUGIN_EXPOSE(SourceMod, g_SourceMod_Core);
|
PLUGIN_EXPOSE(SourceMod, g_SourceMod_Core);
|
||||||
|
|
||||||
|
class Parser : public ITextListener_SMC
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
};
|
||||||
|
|
||||||
bool SourceMod_Core::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
|
bool SourceMod_Core::Load(PluginId id, ISmmAPI *ismm, char *error, size_t maxlen, bool late)
|
||||||
{
|
{
|
||||||
PLUGIN_SAVEVARS();
|
PLUGIN_SAVEVARS();
|
||||||
|
|
||||||
|
Parser p;
|
||||||
|
|
||||||
|
SMCParseError err = g_TextParse.ParseFile_SMC("c:\\debug.txt", &p, NULL, NULL);
|
||||||
|
|
||||||
return g_SourceMod.InitializeSourceMod(error, maxlen, late);
|
return g_SourceMod.InitializeSourceMod(error, maxlen, late);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include "PluginSys.h"
|
#include "PluginSys.h"
|
||||||
#include "LibrarySys.h"
|
#include "LibrarySys.h"
|
||||||
|
#include "sourcemm_api.h"
|
||||||
|
|
||||||
CPluginManager g_PluginMngr;
|
CPluginManager g_PluginMngr;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user