Initial commit
This commit is contained in:
383
engines/grim/textsplit.cpp
Normal file
383
engines/grim/textsplit.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/textconsole.h"
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "engines/grim/textsplit.h"
|
||||
|
||||
namespace Grim {
|
||||
|
||||
static bool isCodeSeparator(char c) {
|
||||
return (c == ' ' || c == ',' || c == '.' || c == '%' || c == '\'' || c == ':');
|
||||
}
|
||||
|
||||
static bool isSeparator(char c) {
|
||||
return (c == ' ' || c == ',' || c == ':');
|
||||
}
|
||||
|
||||
int power(int base, int exp) {
|
||||
int res = 1;
|
||||
for (int i = 0; i < exp; ++i) {
|
||||
res *= base;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static float str2float(const char *str) {
|
||||
int len = strlen(str);
|
||||
int dotpos = len;
|
||||
char *int_part = new char[len + 1];
|
||||
int j = 0;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (str[i] != '.') {
|
||||
int_part[j++] = str[i];
|
||||
} else {
|
||||
dotpos = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
int_part[j++] = '\0';
|
||||
|
||||
// Must use double here. float doesn't have enough precision for the sector
|
||||
// vertices, and the pathfinder may break, like when olivia returns from
|
||||
// the microphone after reciting a poem.
|
||||
double num = atoi(int_part);
|
||||
int sign = (str[0] == '-' ? -1 : 1);
|
||||
j = 0;
|
||||
for (int i = dotpos + 1; i < len; ++i) {
|
||||
double part = (double)(str[i] - 48) / (double)power(10, ++j);
|
||||
num += part * sign;
|
||||
}
|
||||
|
||||
delete[] int_part;
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static bool isNum(char c) {
|
||||
return (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
static char *parseCharacterClass(const char *code, bool *isNegated) {
|
||||
uint32 length = strlen(code);
|
||||
|
||||
char *chars = new char[length];
|
||||
*isNegated = code[1] == '^';
|
||||
uint32 j = 0;
|
||||
uint32 k = (*isNegated ? 2 : 1);
|
||||
|
||||
for (; k < length && code[k] != ']'; ++k) {
|
||||
assert(code[k] != '[' && code[k] != '-');
|
||||
chars[j++] = code[k];
|
||||
}
|
||||
chars[j++] = '\0';
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
// This function is modelled after sscanf, and supports a subset of its features. See sscanf documentation
|
||||
// for information about the syntax it accepts.
|
||||
static void parse(const char *line, const char *fmt, int field_count, va_list va) {
|
||||
char *str = scumm_strdup(line);
|
||||
const int len = strlen(str);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
if (str[i] == '\t')
|
||||
str[i] = ' ';
|
||||
}
|
||||
|
||||
char *format = scumm_strdup(fmt);
|
||||
const int formatlen = strlen(format);
|
||||
for (int i = 0; i < formatlen; ++i) {
|
||||
if (format[i] == '\t')
|
||||
format[i] = ' ';
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
const char *src = str;
|
||||
const char *end = str + len;
|
||||
for (int i = 0; i < formatlen; ++i) {
|
||||
if (format[i] == '%') {
|
||||
char code[10];
|
||||
char width[10];
|
||||
int j = 0;
|
||||
int jw = 0;
|
||||
bool inBrackets = false;
|
||||
while (++i < formatlen && !isCodeSeparator(format[i])) {
|
||||
char c = format[i];
|
||||
if (c == '[') {
|
||||
inBrackets = true;
|
||||
} else if (inBrackets && c == ']') {
|
||||
inBrackets = false;
|
||||
}
|
||||
if (!inBrackets && isNum(c)) {
|
||||
width[jw++] = c;
|
||||
} else {
|
||||
code[j++] = c;
|
||||
}
|
||||
}
|
||||
code[j] = '\0';
|
||||
width[jw] = '\0';
|
||||
|
||||
void *var = va_arg(va, void *);
|
||||
if (strcmp(code, "n") == 0) {
|
||||
*(int*)var = src - str;
|
||||
continue;
|
||||
}
|
||||
|
||||
char s[2000];
|
||||
|
||||
unsigned int fieldWidth = 1;
|
||||
if (width[0] != '\0') {
|
||||
fieldWidth = atoi(width);
|
||||
}
|
||||
|
||||
j = 0;
|
||||
if (code[0] == 'c') {
|
||||
for (unsigned int n = 0; n < fieldWidth; ++n) {
|
||||
s[j++] = src[0];
|
||||
++src;
|
||||
}
|
||||
} else if (code[0] == '[') {
|
||||
bool isNegated;
|
||||
char *allowed = parseCharacterClass(code, &isNegated);
|
||||
|
||||
while (src != end) {
|
||||
bool inSet = strchr(allowed, src[0]) != nullptr;
|
||||
if ((isNegated && inSet) || (!isNegated && !inSet))
|
||||
break;
|
||||
|
||||
s[j++] = src[0];
|
||||
++src;
|
||||
}
|
||||
|
||||
delete[] allowed;
|
||||
} else {
|
||||
char nextChar = format[i];
|
||||
while (src[0] == ' ') { //skip initial whitespace
|
||||
++src;
|
||||
}
|
||||
while (src != end && src[0] != nextChar && !isSeparator(src[0])) {
|
||||
s[j++] = src[0];
|
||||
++src;
|
||||
}
|
||||
}
|
||||
|
||||
s[j] = '\0';
|
||||
--i;
|
||||
|
||||
if (width[0] == '\0') {
|
||||
fieldWidth = strlen(s);
|
||||
}
|
||||
|
||||
if (strcmp(code, "d") == 0) {
|
||||
*(int*)var = atoi(s);
|
||||
} else if (strcmp(code, "x") == 0) {
|
||||
*(int*)var = strtol(s, (char **) nullptr, 16);
|
||||
} else if (strcmp(code, "f") == 0) {
|
||||
*(float*)var = str2float(s);
|
||||
} else if (strcmp(code, "c") == 0) {
|
||||
*(char*)var = s[0];
|
||||
} else if (strcmp(code, "s") == 0) {
|
||||
char *string = (char*)var;
|
||||
strncpy(string, s, fieldWidth);
|
||||
if (fieldWidth <= strlen(s)) {
|
||||
// add terminating \0
|
||||
string[fieldWidth] = '\0';
|
||||
}
|
||||
} else if (code[0] == '[') {
|
||||
char *string = (char*)var;
|
||||
strncpy(string, s, fieldWidth);
|
||||
string[fieldWidth - 1] = '\0';
|
||||
} else {
|
||||
error("Code not handled: \"%s\" \"%s\"\n\"%s\" \"%s\"", code, s, line, fmt);
|
||||
}
|
||||
|
||||
++count;
|
||||
continue;
|
||||
}
|
||||
|
||||
while (src[0] == ' ') {
|
||||
++src;
|
||||
}
|
||||
if (src == end)
|
||||
break;
|
||||
|
||||
if (src[0] != format[i] && format[i] != ' ') {
|
||||
error("Expected line of format '%s', got '%s'", fmt, line);
|
||||
}
|
||||
|
||||
if (src == end)
|
||||
break;
|
||||
if (format[i] != ' ') {
|
||||
++src;
|
||||
if (src == end)
|
||||
break;
|
||||
}
|
||||
}
|
||||
free(str);
|
||||
free(format);
|
||||
|
||||
if (count < field_count) {
|
||||
error("Expected line of format '%s', got '%s'", fmt, line);
|
||||
}
|
||||
}
|
||||
|
||||
TextSplitter::TextSplitter(const Common::String &fname, Common::SeekableReadStream *data) : _fname(fname) {
|
||||
char *line;
|
||||
int i;
|
||||
uint32 len = data->size();
|
||||
|
||||
_stringData = new char[len + 1];
|
||||
data->read(_stringData, len);
|
||||
_stringData[len] = '\0';
|
||||
// Find out how many lines of text there are
|
||||
_numLines = _lineIndex = 0;
|
||||
line = (char *)_stringData;
|
||||
while (line) {
|
||||
line = strchr(line, '\n');
|
||||
if (line) {
|
||||
_numLines++;
|
||||
line++;
|
||||
}
|
||||
}
|
||||
// Allocate an array of the lines
|
||||
_lines = new char *[_numLines];
|
||||
line = (char *)_stringData;
|
||||
for (i = 0; i < _numLines; i++) {
|
||||
char *lastLine = line;
|
||||
line = strchr(lastLine, '\n');
|
||||
*line = '\0';
|
||||
_lines[i] = lastLine;
|
||||
line++;
|
||||
}
|
||||
_currLine = nullptr;
|
||||
processLine();
|
||||
}
|
||||
|
||||
TextSplitter::~TextSplitter() {
|
||||
delete[] _stringData;
|
||||
delete[] _lines;
|
||||
}
|
||||
|
||||
bool TextSplitter::checkString(const char *needle) {
|
||||
// checkString also needs to check for extremely optional
|
||||
// components like "object_art" which can be missing entirely
|
||||
if (!getCurrentLine()) {
|
||||
return false;
|
||||
} else {
|
||||
Common::String haystack(getCurrentLine());
|
||||
Common::String needleStr(needle);
|
||||
haystack.toLowercase();
|
||||
needleStr.toLowercase();
|
||||
return haystack.contains(needleStr);
|
||||
}
|
||||
}
|
||||
|
||||
void TextSplitter::expectString(const char *expected) {
|
||||
if (!_currLine)
|
||||
error("Expected `%s', got EOF on file %s", expected, _fname.c_str());
|
||||
if (scumm_stricmp(getCurrentLine(), expected) != 0)
|
||||
error("Expected `%s', got '%s' on file %s", expected, getCurrentLine(), _fname.c_str());
|
||||
nextLine();
|
||||
}
|
||||
|
||||
void TextSplitter::scanString(const char *fmt, int field_count, ...) {
|
||||
if (!_currLine)
|
||||
error("Expected line of format '%s', got EOF on file %s", fmt, _fname.c_str());
|
||||
|
||||
va_list va;
|
||||
va_start(va, field_count);
|
||||
|
||||
parse(getCurrentLine(), fmt, field_count, va);
|
||||
|
||||
va_end(va);
|
||||
|
||||
nextLine();
|
||||
}
|
||||
|
||||
void TextSplitter::scanStringAtOffset(int offset, const char *fmt, int field_count, ...) {
|
||||
if (!_currLine)
|
||||
error("Expected line of format '%s', got EOF on file %s", fmt, _fname.c_str());
|
||||
|
||||
va_list va;
|
||||
va_start(va, field_count);
|
||||
|
||||
parse(getCurrentLine() + offset, fmt, field_count, va);
|
||||
|
||||
va_end(va);
|
||||
|
||||
nextLine();
|
||||
}
|
||||
|
||||
void TextSplitter::scanStringNoNewLine(const char *fmt, int field_count, ...) {
|
||||
if (!_currLine)
|
||||
error("Expected line of format '%s', got EOF on file %s", fmt, _fname.c_str());
|
||||
|
||||
va_list va;
|
||||
va_start(va, field_count);
|
||||
|
||||
parse(getCurrentLine(), fmt, field_count, va);
|
||||
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void TextSplitter::scanStringAtOffsetNoNewLine(int offset, const char *fmt, int field_count, ...) {
|
||||
if (!_currLine)
|
||||
error("Expected line of format '%s', got EOF on file %s", fmt, _fname.c_str());
|
||||
|
||||
va_list va;
|
||||
va_start(va, field_count);
|
||||
|
||||
parse(getCurrentLine() + offset, fmt, field_count, va);
|
||||
|
||||
va_end(va);
|
||||
}
|
||||
|
||||
void TextSplitter::processLine() {
|
||||
if (isEof())
|
||||
return;
|
||||
|
||||
_currLine = _lines[_lineIndex++];
|
||||
|
||||
// Cut off comments
|
||||
char *comment_start = strchr(_currLine, '#');
|
||||
if (comment_start)
|
||||
*comment_start = '\0';
|
||||
|
||||
// Cut off trailing whitespace (including '\r')
|
||||
char *strend = strchr(_currLine, '\0');
|
||||
while (strend > _currLine && Common::isSpace(strend[-1]))
|
||||
strend--;
|
||||
*strend = '\0';
|
||||
|
||||
// Skip blank lines
|
||||
if (*_currLine == '\0')
|
||||
nextLine();
|
||||
|
||||
// Convert to lower case
|
||||
if (!isEof())
|
||||
for (char *s = _currLine; *s != '\0'; s++)
|
||||
*s = tolower(*s);
|
||||
}
|
||||
|
||||
} // end of namespace Grim
|
||||
Reference in New Issue
Block a user