mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-09 03:10:22 +00:00
384 lines
8.9 KiB
C++
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
|