From 67d36e699a2852ce471c4d1b8dab5992d6c01a98 Mon Sep 17 00:00:00 2001 From: Andreas Stöckel Date: Sat, 24 Jan 2015 03:08:16 +0100 Subject: Implemented SourceContextReader, added unit tests, implemented SourceContextReader interface in ResourceManager, added LoggerTest --- CMakeLists.txt | 2 + src/core/common/SourceContextReader.cpp | 198 ++++++++++++++++ src/core/common/SourceContextReader.hpp | 91 ++++++++ src/core/resource/ResourceManager.cpp | 29 ++- src/core/resource/ResourceManager.hpp | 16 +- test/core/common/CharReaderTest.cpp | 167 -------------- test/core/common/LoggerTest.cpp | 44 +++- test/core/common/SourceContextReaderTest.cpp | 334 +++++++++++++++++++++++++++ 8 files changed, 689 insertions(+), 192 deletions(-) create mode 100644 src/core/common/SourceContextReader.cpp create mode 100644 src/core/common/SourceContextReader.hpp create mode 100644 test/core/common/SourceContextReaderTest.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index ad9c0ad..2ba997d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -130,6 +130,7 @@ ADD_LIBRARY(ousia_core src/core/common/Property src/core/common/Rtti src/core/common/RttiBuilder + src/core/common/SourceContextReader src/core/common/Terminal src/core/common/Utils src/core/common/Variant @@ -221,6 +222,7 @@ IF(TEST) test/core/common/LoggerTest test/core/common/PropertyTest test/core/common/RttiTest + test/core/common/SourceContextReaderTest test/core/common/VariantReaderTest test/core/common/VariantWriterTest test/core/common/VariantTest diff --git a/src/core/common/SourceContextReader.cpp b/src/core/common/SourceContextReader.cpp new file mode 100644 index 0000000..65a6281 --- /dev/null +++ b/src/core/common/SourceContextReader.cpp @@ -0,0 +1,198 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + 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 + +#include +#include + +#include "SourceContextReader.hpp" + +namespace ousia { + +SourceContextReader::SourceContextReader() : cache{0} {} + +SourceContext SourceContextReader::readContext(CharReader &reader, + const SourceRange &range, + size_t maxContextLength, + const std::string &filename) +{ + // Abort if the given range is not valid + if (!range.isValid()) { // (I2) + return SourceContext{}; + } + + // Set the filename and the range + SourceContext ctx; + ctx.startLine = 1; + ctx.startColumn = 1; + ctx.endLine = 1; + ctx.endColumn = 1; + ctx.range = range; + ctx.filename = filename; + + // Some constants for convenience + const SourceOffset start = range.getStart(); + const SourceOffset end = range.getEnd(); + const SourceOffset lastCacheOffs = cache.back(); + + // Find the entry in the cache that is just below the given start offset + // and jump to this location + size_t offs = 0; + auto it = std::lower_bound(cache.begin(), cache.end(), start); + if (it != cache.begin()) { + it--; // Go to the previous entry + offs = *it; // Read the corresponding byte offset + size_t line = it - cache.begin() + 1; + ctx.startLine = line; + ctx.endLine = line; + } + + // Move the char reader to the specified offset, abort if this did not work + // out + if (offs != reader.seek(offs)) { + return SourceContext{}; + } + + // TODO: Handle skew introduced by linebreak processing \n\r => \n + + // Read until the requested byte offset is reached, track linebreaks in the + // linebreak cache + std::vector lineBuf; + size_t lineBufStart = offs; + size_t lastLineStart = offs; + char c; + while (reader.read(c)) { + // Fetch the offset after this character + const size_t nextOffs = reader.getOffset(); + + // Fetch the current offset, check whether start was reached + const bool reachedStart = offs >= start; + const bool reachedEnd = offs >= end; + + // Handle linebreaks and update the linebreak cache + if (c == '\n') { + // Update the linebreak cache if we are in uncached regions + if (offs > lastCacheOffs) { + cache.push_back(nextOffs); + } + if (!reachedStart) { + ctx.startLine++; + ctx.startColumn = 1; + lineBuf.clear(); + lineBufStart = nextOffs; + lastLineStart = nextOffs; + } else { + lineBuf.push_back('\n'); + } + if (!reachedEnd) { + ctx.endLine++; + ctx.endColumn = 1; + } else { + // This was the last character, abort + break; + } + } else { + // Increment the start and the end column if this is not an + // UTF8-continuation byte (note that we count unicode codepoints not + // actual characters, which may be more than one codepoint) + if (!((c & 0x80) && !(c & 0x40))) { + if (!reachedStart) { + ctx.startColumn++; + } + if (!reachedEnd) { + ctx.endColumn++; + } + } + + // Record all characters when start is reached or at least when + // the distance to start is smaller than the maximum context length + // TODO: This is suboptimal as parts of lineBuf are thrown away + // later. If the given range is really large, this will waste huge + // amounts of RAM. + if (reachedStart || (start - offs <= maxContextLength)) { + if (lineBuf.empty()) { + lineBufStart = offs; + } + lineBuf.push_back(c); + } + } + + // Set the new offset + offs = nextOffs; + } + + // If we did not reach the end or for some reason the lineBufStart is larger + // than start (to assure invariant I1 is fulfilled), abort + offs = reader.getOffset(); + if (offs < end || lineBufStart > start) { // (I1) + return SourceContext{}; + } + + // Calculate a first relative position and length + ctx.relPos = start - lineBufStart; // lineBufStart > start (I1) + ctx.relLen = end - start; // end >= start (I2) + + // Remove linebreaks at the beginning and the end + const std::pair b = + Utils::trim(lineBuf, Utils::isLinebreak); + ssize_t s = b.first, e = b.second; + s = std::min(s, static_cast(ctx.relPos)); + + // Remember the trimmed positions, only continue if the context text did + // not entirely consist of linebreaks + const ssize_t ts = s, te = e; // s >= 0, e >= 0, ts >= 0, te >= 0 (I3) + if (te > ts) { + // Trim the line further if it is longer than the maxContextLength + if (static_cast(te - ts) > maxContextLength && + maxContextLength != MAX_MAX_CONTEXT_LENGTH) { + ssize_t c = (ctx.relPos + ctx.relLen / 2); + s = c - maxContextLength / 2; + e = c + maxContextLength / 2; + + // Account for rounding error + if (static_cast(e - s) < maxContextLength) { + e++; + } + + // Redistribute available characters at the beginning or the end + if (s < ts) { + e = e + (ts - s); + s = ts; // ts >= 0 => s >= 0 (I3) + } + if (e > te) { + s = s - std::min(s - ts, e - te); // ts - s <= s => s >= 0 + e = te; // te >= 0 => e >= 0 (I3) + } + } + + // Update the relative position and length, set the "truncated" flags + size_t us = static_cast(s), ue = static_cast(e); + ctx.relPos = start - lineBufStart - us; + ctx.relLen = std::min(ctx.relLen, ue - us); + ctx.truncatedStart = s > ts || lastLineStart < lineBufStart; + ctx.truncatedEnd = e < te; + + // Copy the selected area to the output string + ctx.text = std::string{&lineBuf[s], ue - us}; + } + + return ctx; +} +} + diff --git a/src/core/common/SourceContextReader.hpp b/src/core/common/SourceContextReader.hpp new file mode 100644 index 0000000..35e71b3 --- /dev/null +++ b/src/core/common/SourceContextReader.hpp @@ -0,0 +1,91 @@ +/* + Ousía + Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel + + 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 . +*/ + +/** + * @file SourceContextReader.hpp + * + * The SourceContextReader class is used to read a SourceContext struct from + * a SourcePosition instance and an input stream. + * + * @author Andreas Stöckel (astoecke@techfak.uni-bielefeld.de) + */ + +#ifndef _OUSIA_SOURCE_CONTEXT_READER_HPP_ +#define _OUSIA_SOURCE_CONTEXT_READER_HPP_ + +#include +#include +#include + +#include "Location.hpp" + +namespace ousia { + +// Forward declarations +class CharReader; + +/** + * The SourceContextReader can read SourceContext structures given a + * SourcePosition or SourceRange and a char reader. It is capable of managing + * a line number cache which speeds up repeated context lookups. + */ +class SourceContextReader { +private: + /** + * Cache containing the byte offset of each line break. + */ + std::vector cache; + +public: + /** + * Maximum context size. Used to indicate that the context should have an + * unlimited size. + */ + static constexpr size_t MAX_MAX_CONTEXT_LENGTH = + std::numeric_limits::max(); + + /** + * Default constructor. Initializes the internal lineNumberCache with a + * single zero entry. + */ + SourceContextReader(); + + /** + * Returns the context for the char reader and the given SourceRange. + * Returns an invalid source context if either the given range is invalid + * or the byte offset described in the SourceRange cannot be reached because + * the CharReader cannot be seeked back to this position. + * + * @param reader is the CharReader instance from which the context should be + * read. + * @param range describes the Range within the source file for which the + * context should be extraced. + * @param filename is the filename that should be stored in the returned + * context. + * @param maxContextLength is the maximum number of characters that should + * be stored in the returned context. + * @return a SourceContext instance describing the + */ + SourceContext readContext(CharReader &reader, const SourceRange &range, + size_t maxContextLength = MAX_MAX_CONTEXT_LENGTH, + const std::string &filename = ""); +}; +} + +#endif /* _OUSIA_SOURCE_CONTEXT_READER_HPP_ */ + diff --git a/src/core/resource/ResourceManager.cpp b/src/core/resource/ResourceManager.cpp index f154c9c..a5e76b0 100644 --- a/src/core/resource/ResourceManager.cpp +++ b/src/core/resource/ResourceManager.cpp @@ -35,7 +35,8 @@ namespace ousia { /* Static helper functions */ -static void logUnsopportedType(Logger &logger, Resource &resource, const RttiSet &supportedTypes) +static void logUnsopportedType(Logger &logger, Resource &resource, + const RttiSet &supportedTypes) { // Build a list containing the expected type names std::vector expected; @@ -81,7 +82,7 @@ void ResourceManager::purgeResource(SourceId sourceId) } resources.erase(sourceId); nodes.erase(sourceId); - lineNumberCache.erase(sourceId); + contextReaders.erase(sourceId); } Rooted ResourceManager::parse(ParserContext &ctx, Resource &resource, @@ -93,7 +94,8 @@ Rooted ResourceManager::parse(ParserContext &ctx, Resource &resource, if (mime.empty()) { mime = ctx.registry.getMimetypeForFilename(resource.getLocation()); if (mime.empty()) { - ctx.logger.error(std::string("Filename \"") + resource.getLocation() + + ctx.logger.error(std::string("Filename \"") + + resource.getLocation() + std::string( "\" has an unknown file extension. Explicitly " "specify a mimetype.")); @@ -137,7 +139,8 @@ Rooted ResourceManager::parse(ParserContext &ctx, Resource &resource, if (node == nullptr) { throw LoggableException{"Internal error: Parser returned null."}; } - } catch (LoggableException ex) { + } + catch (LoggableException ex) { // Remove all data associated with the allocated source id purgeResource(sourceId); @@ -262,14 +265,20 @@ Rooted ResourceManager::link(ParserContext &ctx, const std::string &path, return link(ctx, path, mimetype, rel, supportedTypes, relativeResource); } -SourceContext ResourceManager::buildContext(const SourceLocation &location) +SourceContext ResourceManager::readContext(const SourceLocation &location, + size_t maxContextLength) { - SourceContext res; - - // TODO + const Resource &resource = getResource(location.getSourceId()); + if (resource.isValid()) { + // Fetch a char reader for the resource + std::unique_ptr is = resource.stream(); + CharReader reader{*is, location.getSourceId()}; - return res; + // Return the context + return contextReaders[location.getSourceId()].readContext( + reader, location, maxContextLength, resource.getLocation()); + } + return SourceContext{}; } - } diff --git a/src/core/resource/ResourceManager.hpp b/src/core/resource/ResourceManager.hpp index 51c00e3..d5381b9 100644 --- a/src/core/resource/ResourceManager.hpp +++ b/src/core/resource/ResourceManager.hpp @@ -34,6 +34,7 @@ #include #include +#include #include #include "Resource.hpp" @@ -74,11 +75,11 @@ private: std::unordered_map nodes; /** - * Cache used for translating byte offsets to line numbers. Maps from a - * SourceId onto a list of (sorted) SourceOffsets. The index in the list - * corresponds to the line number. + * Map containing SourceContextReader instances which are -- as their name + * suggests -- used to produce SourceContext structures describing the + * source code at a given SourceLocation. */ - std::unordered_map> lineNumberCache; + std::unordered_map contextReaders; /** * Allocates a new SourceId for the given resource. @@ -224,11 +225,14 @@ public: * @param location is the SourceLocation for which context information * should be retrieved. This method is used by the Logger class to print * pretty messages. + * @param maxContextLength is the maximum length in character of context + * that should be extracted. * @return a valid SourceContext if a valid SourceLocation was given or an * invalid SourceContext if the location is invalid. */ - SourceContext buildContext(const SourceLocation &location); - + SourceContext readContext( + const SourceLocation &location, + size_t maxContextLength = SourceContextReader::MAX_MAX_CONTEXT_LENGTH); }; } diff --git a/test/core/common/CharReaderTest.cpp b/test/core/common/CharReaderTest.cpp index fba60f9..a1ea18f 100644 --- a/test/core/common/CharReaderTest.cpp +++ b/test/core/common/CharReaderTest.cpp @@ -565,172 +565,5 @@ TEST(CharReader, fork) ASSERT_EQ(5, reader.getOffset()); } -/*TEST(CharReader, context) -{ - std::string testStr{"first line\n\n\rsecond line\n\rlast line"}; - // 0123456789 0 123456789012 3456789012 - // 0 1 2 3 - - // Retrieval at beginning of stream - { - CharReader reader{testStr}; - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("first line", ctx.text); - ASSERT_EQ(0, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Retrieval in middle of line - { - CharReader reader{testStr}; - SourceContext ctx = reader.getContext(80); - - char c; - for (int i = 0; i < 5; i++) - reader.read(c); - - ASSERT_EQ("first line", ctx.text); - ASSERT_EQ(0, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Retrieval in whitespace sequence - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 11; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("first line", ctx.text); - ASSERT_EQ(10, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Truncation of text - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 5; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(3); - ASSERT_EQ("t l", ctx.text); - ASSERT_EQ(1, ctx.relPos); - ASSERT_TRUE(ctx.truncatedStart); - ASSERT_TRUE(ctx.truncatedEnd); - } - - // Second line - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 12; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("second line", ctx.text); - ASSERT_EQ(0, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // End of second line - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 23; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("second line", ctx.text); - ASSERT_EQ(11, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Last line - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 24; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("last line", ctx.text); - ASSERT_EQ(0, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Middle of last line - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 28; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("last line", ctx.text); - ASSERT_EQ(4, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // Middle of last line truncated - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 28; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(3); - ASSERT_EQ("t l", ctx.text); - ASSERT_EQ(1, ctx.relPos); - ASSERT_TRUE(ctx.truncatedStart); - ASSERT_TRUE(ctx.truncatedEnd); - } - - // End of stream - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 100; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(80); - ASSERT_EQ("last line", ctx.text); - ASSERT_EQ(9, ctx.relPos); - ASSERT_FALSE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } - - // End of stream truncated - { - CharReader reader{testStr}; - - char c; - for (int i = 0; i < 100; i++) - reader.read(c); - - SourceContext ctx = reader.getContext(4); - ASSERT_EQ("line", ctx.text); - ASSERT_EQ(4, ctx.relPos); - ASSERT_TRUE(ctx.truncatedStart); - ASSERT_FALSE(ctx.truncatedEnd); - } -}*/ - } diff --git a/test/core/common/LoggerTest.cpp b/test/core/common/LoggerTest.cpp index 9b20cc6..7b0e6d0 100644 --- a/test/core/common/LoggerTest.cpp +++ b/test/core/common/LoggerTest.cpp @@ -20,28 +20,51 @@ #include +#include #include +#include namespace ousia { struct Pos { SourceLocation pos; - Pos(SourceLocation pos = SourceLocation{}) - : pos(pos) {}; + Pos(SourceLocation pos = SourceLocation{}) : pos(pos){}; SourceLocation getLocation() { return pos; } }; +static const std::string testStr = + "\\link[domain]{book}\n" // 1 + "\\link[domain]{meta}\n" // 2 + "\n" // 3 + "\\meta{\n" // 4 + "\t\\title{The Adventures Of Tom Sawyer}\n" // 5 + "\t\\author{Mark Twain}\n" // 6 + "}\n" // 7 + "\n" // 8 + "\\book{\n" // 9 + "\n" // 10 + "\n" // 11 + "\\chapter\n" // 12 + "<>\n" // 13 + "\n" // 14 + "No answer.\n" // 15 + "\n" // 16 + "<>\n" // 17 + "\n" // 18 + "No answer.\n" // 19 + "\n" // 20 + "<>\n" // 21 + "}\n"; // 22 + +static SourceContextReader contextReader{}; + static SourceContext contextCallback(const SourceLocation &location) { - SourceContext ctx; - ctx.filename = "testfile.test"; - ctx.startLine = 10; - ctx.endLine = 10; - ctx.startColumn = 20; - ctx.endColumn = 20; - return ctx; + CharReader reader{testStr, 0}; + return contextReader.readContext(reader, location, 60, + "the_adventures_of_tom_sawyer.opd"); } TEST(TerminalLogger, log) @@ -52,6 +75,9 @@ TEST(TerminalLogger, log) logger.debug("This is a test debug message"); logger.note("This is a test note"); + logger.note("This is a test note with point context", SourceLocation{0, 49}); + logger.note("This is a test note with range context", SourceLocation{0, 49, 55}); + logger.note("This is a test note with multiline context", SourceLocation{0, 49, 150}); logger.warning("This is a test warning"); logger.error("This is a test error"); logger.fatalError("This is a test fatal error!"); diff --git a/test/core/common/SourceContextReaderTest.cpp b/test/core/common/SourceContextReaderTest.cpp new file mode 100644 index 0000000..4d74e67 --- /dev/null +++ b/test/core/common/SourceContextReaderTest.cpp @@ -0,0 +1,334 @@ +/* + Ousía + Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel + + 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 + +#include +#include + +namespace ousia { + +static const std::string testStr{"first line\n\nthird line\nlast line"}; +// 0123456789 0 12345678901 23456789012 +// 0 1 2 3 + +static SourceContext readContext( + SourceContextReader &sr, size_t pos, + size_t width = SourceContextReader::MAX_MAX_CONTEXT_LENGTH) +{ + CharReader reader{testStr}; + return sr.readContext(reader, SourceRange{pos}, width); +} + +TEST(SourceContextReader, firstLine) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 0); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("first line", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(1U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, firstLineCenter) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 5); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("first line", ctx.text); + EXPECT_EQ(5U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(6U, ctx.startColumn); + EXPECT_EQ(1U, ctx.endLine); + EXPECT_EQ(6U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, firstLineBeginTruncated) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 0, 3); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("fir", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(1U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_TRUE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, inWhitespaceSequence) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 10); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("first line", ctx.text); + EXPECT_EQ(10U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(11U, ctx.startColumn); + EXPECT_EQ(1U, ctx.endLine); + EXPECT_EQ(11U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, truncation) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 5, 3); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("t l", ctx.text); + EXPECT_EQ(1U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(6U, ctx.startColumn); + EXPECT_EQ(1U, ctx.endLine); + EXPECT_EQ(6U, ctx.endColumn); + EXPECT_TRUE(ctx.truncatedStart); + EXPECT_TRUE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, emptyLine) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 11); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(2U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(2U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, thirdLine) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 12); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("third line", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(3U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(3U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, thirdLineBeginTruncated) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 12, 3); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("thi", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(3U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(3U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_TRUE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, thirdLineEnd) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 22); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("third line", ctx.text); + EXPECT_EQ(10U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(3U, ctx.startLine); + EXPECT_EQ(11U, ctx.startColumn); + EXPECT_EQ(3U, ctx.endLine); + EXPECT_EQ(11U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLine) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 23); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("last line", ctx.text); + EXPECT_EQ(0U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(4U, ctx.startLine); + EXPECT_EQ(1U, ctx.startColumn); + EXPECT_EQ(4U, ctx.endLine); + EXPECT_EQ(1U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLineMiddle) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 27); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("last line", ctx.text); + EXPECT_EQ(4U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(4U, ctx.startLine); + EXPECT_EQ(5U, ctx.startColumn); + EXPECT_EQ(4U, ctx.endLine); + EXPECT_EQ(5U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLineMiddleTruncated) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 27, 3); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("t l", ctx.text); + EXPECT_EQ(1U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(4U, ctx.startLine); + EXPECT_EQ(5U, ctx.startColumn); + EXPECT_EQ(4U, ctx.endLine); + EXPECT_EQ(5U, ctx.endColumn); + EXPECT_TRUE(ctx.truncatedStart); + EXPECT_TRUE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLineEnd) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 32); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("last line", ctx.text); + EXPECT_EQ(9U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(4U, ctx.startLine); + EXPECT_EQ(10U, ctx.startColumn); + EXPECT_EQ(4U, ctx.endLine); + EXPECT_EQ(10U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLineEndTruncated) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 32, 3); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("ine", ctx.text); + EXPECT_EQ(3U, ctx.relPos); + EXPECT_EQ(0U, ctx.relLen); + EXPECT_EQ(4U, ctx.startLine); + EXPECT_EQ(10U, ctx.startColumn); + EXPECT_EQ(4U, ctx.endLine); + EXPECT_EQ(10U, ctx.endColumn); + EXPECT_TRUE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +TEST(SourceContextReader, lastLineBeyondEnd) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + SourceContext ctx = readContext(sr, 33); + EXPECT_FALSE(ctx.isValid()); + } +} + +TEST(SourceContextReader, multiline) +{ + SourceContextReader sr; + for (int i = 0; i < 2; i++) { + CharReader reader{testStr}; + SourceContext ctx = sr.readContext(reader, SourceRange{5, 17}); + + EXPECT_TRUE(ctx.isValid()); + EXPECT_EQ("first line\n\nthird line", ctx.text); + EXPECT_EQ(5U, ctx.relPos); + EXPECT_EQ(12U, ctx.relLen); + EXPECT_EQ(1U, ctx.startLine); + EXPECT_EQ(6U, ctx.startColumn); + EXPECT_EQ(3U, ctx.endLine); + EXPECT_EQ(6U, ctx.endColumn); + EXPECT_FALSE(ctx.truncatedStart); + EXPECT_FALSE(ctx.truncatedEnd); + } +} + +} + -- cgit v1.2.3