scummvm/engines/grim/textsplit.cpp
Paweł Kołodziejski 06902574b4
GRIM: Janitorial
2022-06-08 01:12:00 +02:00

384 lines
8.9 KiB
C++

/* 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