/* 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 . * */ #include "common/path.h" #include "common/stream.h" #include "common/textconsole.h" #include "vcruise/textparser.h" namespace VCruise { TextParserState::TextParserState() : _lineNum(1), _col(1), _prevWasCR(false), _isParsingComment(false) { } TextParser::TextParser(Common::ReadStream *stream) : _stream(stream), _returnedBufferPos(kReturnedBufferSize) { memset(_returnedBuffer, 0, kReturnedBufferSize); } bool TextParser::readOneChar(char &outC, TextParserState &outState) { if (_returnedBufferPos == kReturnedBufferSize) { if (_stream->eos()) return false; } char c = 0; if (_returnedBufferPos != kReturnedBufferSize) { c = _returnedBuffer[_returnedBufferPos++]; } else { if (!_stream->read(&c, 1)) return false; } TextParserState prevState = _state; if (c == '\r') { _state._lineNum++; _state._col = 1; _state._isParsingComment = false; _state._prevWasCR = true; } else if (c == '\n') { if (!_state._prevWasCR) { _state._lineNum++; _state._col = 1; } _state._prevWasCR = false; } else { _state._col++; _state._prevWasCR = false; if (c == ';') _state._isParsingComment = true; } outC = c; outState = prevState; return true; } bool TextParser::skipWhitespaceAndComments(char &outC, TextParserState &outState) { char c = 0; TextParserState firstCharState; while (readOneChar(c, firstCharState)) { if (isWhitespace(c)) continue; if (_state._isParsingComment) continue; outC = c; outState = firstCharState; return true; } return false; } bool TextParser::isDelimiter(char c) { if (c == ',' || c == '=' || c == '[' || c == ']') return true; return false; } bool TextParser::isCompoundDelimiter(char c1, char c2) { if (c2 == '=' && (c1 == '=' || c1 == '<' || c1 == '>' || c1 == '!')) return true; return false; } bool TextParser::isWhitespace(char c) { return (c == ' ') || ((c & 0xe0) == 0); } void TextParser::requeue(const char *chars, uint numChars, const TextParserState &state) { _state = state; assert(_returnedBufferPos >= numChars); _returnedBufferPos -= numChars; memcpy(_returnedBuffer + _returnedBufferPos, chars, numChars); } void TextParser::requeue(const Common::String &str, const TextParserState &state) { requeue(str.c_str(), str.size(), state); } void TextParser::expectToken(Common::String &outToken, const Common::Path &blamePath) { TextParserState state; expectTokenInternal(outToken, blamePath, state); } void TextParser::expectShort(int16 &outInt, const Common::Path &blamePath) { int i; expectInt(i, blamePath); outInt = static_cast(i); } void TextParser::expectInt(int &outInt, const Common::Path &blamePath) { Common::String token; TextParserState state; expectTokenInternal(token, blamePath, state); if (!sscanf(token.c_str(), "%i", &outInt)) error("Parsing error in '%s' at line %i col %i: Integer was malformed", blamePath.toString(Common::Path::kNativeSeparator).c_str(), static_cast(state._lineNum), static_cast(state._col)); } void TextParser::expectUInt(uint &outUInt, const Common::Path &blamePath) { Common::String token; TextParserState state; expectTokenInternal(token, blamePath, state); if (!sscanf(token.c_str(), "%u", &outUInt)) error("Parsing error in '%s' at line %i col %i: Unsigned integer was malformed", blamePath.toString(Common::Path::kNativeSeparator).c_str(), static_cast(state._lineNum), static_cast(state._col)); } void TextParser::expectLine(Common::String &outToken, const Common::Path &blamePath, bool continueToNextLine) { outToken.clear(); char c = 0; TextParserState state; bool isSkippingWhitespace = true; uint nonWhitespaceLength = 0; while (readOneChar(c, state)) { if (c == '\r' || c == '\n' || _state._isParsingComment) { requeue(&c, 1, state); if (continueToNextLine) skipToEOL(); break; } bool cIsWhitespace = isWhitespace(c); if (isSkippingWhitespace) { if (cIsWhitespace) continue; isSkippingWhitespace = false; } outToken += c; if (!cIsWhitespace) nonWhitespaceLength = outToken.size(); } if (nonWhitespaceLength != outToken.size()) outToken = outToken.substr(0, nonWhitespaceLength); } void TextParser::expect(const char *str, const Common::Path &blamePath) { Common::String token; TextParserState state; expectTokenInternal(token, blamePath, state); if (token != str) error("Parsing error in '%s' at line %i col %i: Expected token '%s' but found '%s'", blamePath.toString(Common::Path::kNativeSeparator).c_str(), static_cast(state._lineNum), static_cast(state._col), str, token.c_str()); } void TextParser::skipToEOL() { char c = 0; TextParserState state; while (readOneChar(c, state)) { if (c == '\n') return; if (c == '\r') { if (readOneChar(c, state)) { if (c != '\n') requeue(&c, 1, state); return; } } } } bool TextParser::checkEOL() { char c = 0; TextParserState state; for (;;) { if (!readOneChar(c, state)) return true; if (_state._isParsingComment || c == '\n' || c == '\r') { // EOL or comment requeue(&c, 1, state); return true; } if (!isWhitespace(c)) { // Non-whitespace requeue(&c, 1, state); return false; } } } void TextParser::expectTokenInternal(Common::String &outToken, const Common::Path &blamePath, TextParserState &outState) { if (!parseToken(outToken, outState)) error("Parsing error in '%s' unexpected end of file", blamePath.toString(Common::Path::kNativeSeparator).c_str()); } bool TextParser::parseToken(Common::String &outString, TextParserState &outState) { outString.clear(); char c = 0; TextParserState state; if (!skipWhitespaceAndComments(c, state)) return false; outState = state; outString += c; if (c == '\"') { while (readOneChar(c, state)) { if (c == '\n' || c == '\r') { requeue(&c, 1, state); return true; } outString += c; if (c == '\"') return true; } return true; } if (isDelimiter(c)) { char firstC = c; if (readOneChar(c, state)) { if (isCompoundDelimiter(firstC, c)) outString += c; else requeue(&c, 1, state); } return true; } while (readOneChar(c, state)) { if (isWhitespace(c) || _state._isParsingComment) { requeue(&c, 1, state); break; } if (outString.size() == 1 && isCompoundDelimiter(outString[0], c)) { outString += c; break; } if (isDelimiter(c)) { requeue(&c, 1, state); break; } outString += c; } return true; } } // End of namespace VCruise