236 lines
5.7 KiB
C++
236 lines
5.7 KiB
C++
#include "TextReader.hpp"
|
|
|
|
#include <spider/compiler/text/utf8.hpp>
|
|
|
|
#include <stdexcept>
|
|
|
|
namespace spider {
|
|
|
|
// Text Reader //
|
|
|
|
TextReader::TextReader() : err(false), eof(false), bufferIndex(0) {
|
|
// Prime the buffer with the first character
|
|
// so current() is immediately valid
|
|
fillBufferTo(0);
|
|
}
|
|
|
|
TextReader::~TextReader() {}
|
|
|
|
bool TextReader::eat(u32 _char) {
|
|
if(current() == _char) {
|
|
nextChar();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool TextReader::eat(char _char) {
|
|
return this->eat(u32(_char));
|
|
}
|
|
|
|
bool TextReader::eat(const std::string& chars) {
|
|
// instead of whatever that was, convert to UTF-32
|
|
// and then do an easy compare!
|
|
std::u32string str;
|
|
if(!utf8::toUTF32(chars, str)) throw std::runtime_error("Specified invalid UTF-8 string!");
|
|
return eat(str);
|
|
}
|
|
|
|
bool TextReader::eat(const std::u32string& str) {
|
|
// compare now
|
|
isize index;
|
|
for(index = 0; index < str.size(); index++) {
|
|
if(str[index] != peekChar(index)) return false;
|
|
}
|
|
|
|
// success!
|
|
nextChar(index);
|
|
return true;
|
|
}
|
|
|
|
char TextReader::readByte() {
|
|
if (err) return 0;
|
|
auto& s = getStream();
|
|
|
|
if(s.bad()) {
|
|
err = true;
|
|
errmsg = "Stream raised bad bit.";
|
|
return 0;
|
|
}
|
|
|
|
int ch = s.get();
|
|
if (ch == std::istream::traits_type::eof()) {
|
|
eof = true;
|
|
return 0;
|
|
}
|
|
|
|
at.byteoff++;
|
|
return char(ch);
|
|
}
|
|
|
|
/**
|
|
* Returns the current character.
|
|
*/
|
|
optional<u32> TextReader::current() {
|
|
if (bufferIndex < buffer.size()) {
|
|
return buffer[bufferIndex];
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Reads the next character and advances the position tracker.
|
|
*/
|
|
optional<u32> TextReader::nextChar(isize n) {
|
|
// Ensure the character we are moving TO exists
|
|
if (fillBufferTo(n)) {
|
|
// advance n characters
|
|
while(n--) {
|
|
advance(buffer[bufferIndex]);
|
|
bufferIndex++;
|
|
}
|
|
return current();
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* Keeps the next n-th character (n = 0 is current).
|
|
*/
|
|
optional<u32> TextReader::peekChar(isize n) {
|
|
if (fillBufferTo(n)) return buffer[bufferIndex + n];
|
|
return {};
|
|
}
|
|
|
|
void TextReader::commit() {
|
|
if (bufferIndex > 0) {
|
|
// Erase everything before the current buffer index
|
|
buffer.erase(buffer.begin(), buffer.begin() + std::ptrdiff_t(bufferIndex));
|
|
bufferIndex = 0;
|
|
}
|
|
}
|
|
|
|
isize TextReader::push() {
|
|
return bufferIndex;
|
|
}
|
|
|
|
void TextReader::pop(isize index) {
|
|
bufferIndex = std::min(index, bufferIndex);
|
|
}
|
|
|
|
/**
|
|
* Updates track position metrics based on the processed character.
|
|
*/
|
|
void TextReader::advance(u32 ch) {
|
|
if (ch == '\n') {
|
|
at.line++;
|
|
at.col = 1;
|
|
} else {
|
|
at.col++;
|
|
}
|
|
}
|
|
|
|
bool TextReader::readChar() {
|
|
// Read one byte
|
|
char bytes[4];
|
|
isize bindex = 0;
|
|
|
|
bytes[bindex] = readByte();
|
|
if (err) return false;
|
|
if (eof) return false;
|
|
|
|
isize chsize = utf8::seqlen(u8(bytes[bindex]));
|
|
if(chsize == 0) {
|
|
err = true;
|
|
errmsg = "Invalid start of UTF-8 sequence.";
|
|
return false;
|
|
}
|
|
bindex++;
|
|
|
|
while (bindex < chsize) {
|
|
bytes[bindex] = readByte();
|
|
if (err) return false;
|
|
if (eof) return false;
|
|
}
|
|
|
|
u32 decodedChar;
|
|
if(!utf8::decodeArr(bytes, chsize, decodedChar)) {
|
|
err = true;
|
|
errmsg = "Invalid UTF-8 sequence.";
|
|
return false;
|
|
}
|
|
buffer.push_back(decodedChar);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Fills the buffer sequentially until it contains at least up
|
|
* to (bufferIndex + targetOffset).
|
|
*/
|
|
bool TextReader::fillBufferTo(isize targetOffset) {
|
|
isize targetSize = bufferIndex + targetOffset + 1;
|
|
while (buffer.size() < targetSize) {
|
|
if(readChar()) continue;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
pos TextReader::getPosition() const {
|
|
return at;
|
|
}
|
|
|
|
bool TextReader::isEOF() const{
|
|
return eof && bufferIndex >= buffer.size();
|
|
}
|
|
|
|
bool TextReader::hasError() const{
|
|
return err;
|
|
}
|
|
|
|
TextReader::operator bool() const {
|
|
return !isEOF() && !hasError();
|
|
}
|
|
|
|
std::string TextReader::getError() const {
|
|
return errmsg;
|
|
}
|
|
|
|
// File Reader //
|
|
|
|
FileTextReader::FileTextReader(const std::string& filename)
|
|
: fileStream(filename, std::ios::binary) {
|
|
if (!fileStream.is_open()) {
|
|
throw std::runtime_error("Failed to open file: " + filename);
|
|
}
|
|
}
|
|
|
|
std::istream& FileTextReader::getStream() {
|
|
return fileStream;
|
|
}
|
|
|
|
// String Reader //
|
|
|
|
StringTextReader::StringTextReader(std::string initialText)
|
|
: buffer(std::move(initialText)),
|
|
stringStream(std::make_unique<std::istringstream>(buffer)) {
|
|
}
|
|
|
|
std::istream& StringTextReader::getStream() {
|
|
return *stringStream;
|
|
}
|
|
|
|
void StringTextReader::set(const std::string& newText) {
|
|
buffer = newText;
|
|
stringStream = std::make_unique<std::istringstream>(buffer);
|
|
}
|
|
|
|
void StringTextReader::append(const std::string& extraText) {
|
|
std::streampos pos = stringStream->tellg();
|
|
buffer += extraText;
|
|
stringStream = std::make_unique<std::istringstream>(buffer);
|
|
stringStream->seekg(pos);
|
|
}
|
|
|
|
}
|