diff --git a/src/libutil/position.cc b/src/libutil/position.cc index b39a5a1d4..724e560b7 100644 --- a/src/libutil/position.cc +++ b/src/libutil/position.cc @@ -29,32 +29,17 @@ std::optional Pos::getCodeLines() const return std::nullopt; if (auto source = getSource()) { - - std::istringstream iss(*source); - // count the newlines. - int count = 0; - std::string curLine; - int pl = line - 1; - + LinesIterator lines(*source), end; LinesOfCode loc; - do { - std::getline(iss, curLine); - ++count; - if (count < pl) - ; - else if (count == pl) { - loc.prevLineOfCode = curLine; - } else if (count == pl + 1) { - loc.errLineOfCode = curLine; - } else if (count == pl + 2) { - loc.nextLineOfCode = curLine; - break; - } - - if (!iss.good()) - break; - } while (true); + if (line > 1) + std::advance(lines, line - 2); + if (lines != end && line > 1) + loc.prevLineOfCode = *lines++; + if (lines != end) + loc.errLineOfCode = *lines++; + if (lines != end) + loc.nextLineOfCode = *lines++; return loc; } @@ -109,4 +94,26 @@ std::ostream & operator<<(std::ostream & str, const Pos & pos) return str; } +void Pos::LinesIterator::bump(bool atFirst) +{ + if (!atFirst) { + pastEnd = input.empty(); + if (!input.empty() && input[0] == '\r') + input.remove_prefix(1); + if (!input.empty() && input[0] == '\n') + input.remove_prefix(1); + } + + // nix line endings are not only \n as eg std::getline assumes, but also + // \r\n **and \r alone**. not treating them all the same causes error + // reports to not match with line numbers as the parser expects them. + auto eol = input.find_first_of("\r\n"); + + if (eol > input.size()) + eol = input.size(); + + curLine = input.substr(0, eol); + input.remove_prefix(eol); +} + } diff --git a/src/libutil/position.hh b/src/libutil/position.hh index a184997ed..9bdf3b4b5 100644 --- a/src/libutil/position.hh +++ b/src/libutil/position.hh @@ -67,6 +67,48 @@ struct Pos bool operator==(const Pos & rhs) const = default; bool operator!=(const Pos & rhs) const = default; bool operator<(const Pos & rhs) const; + + struct LinesIterator { + using difference_type = size_t; + using value_type = std::string_view; + using reference = const std::string_view &; + using pointer = const std::string_view *; + using iterator_category = std::input_iterator_tag; + + LinesIterator(): pastEnd(true) {} + explicit LinesIterator(std::string_view input): input(input), pastEnd(input.empty()) { + if (!pastEnd) + bump(true); + } + + LinesIterator & operator++() { + bump(false); + return *this; + } + LinesIterator operator++(int) { + auto result = *this; + ++*this; + return result; + } + + reference operator*() const { return curLine; } + pointer operator->() const { return &curLine; } + + bool operator!=(const LinesIterator & other) const { + return !(*this == other); + } + bool operator==(const LinesIterator & other) const { + return (pastEnd && other.pastEnd) + || (std::forward_as_tuple(input.size(), input.data()) + == std::forward_as_tuple(other.input.size(), other.input.data())); + } + + private: + std::string_view input, curLine; + bool pastEnd = false; + + void bump(bool atFirst); + }; }; std::ostream & operator<<(std::ostream & str, const Pos & pos); diff --git a/tests/functional/lang/eval-fail-eol-1.err.exp b/tests/functional/lang/eval-fail-eol-1.err.exp new file mode 100644 index 000000000..3f5a5c22c --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-1.err.exp @@ -0,0 +1,6 @@ +error: undefined variable 'invalid' + at /pwd/lang/eval-fail-eol-1.nix:2:1: + 1| # foo + 2| invalid + | ^ + 3| # bar diff --git a/tests/functional/lang/eval-fail-eol-1.nix b/tests/functional/lang/eval-fail-eol-1.nix new file mode 100644 index 000000000..476223919 --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-1.nix @@ -0,0 +1,3 @@ +# foo +invalid +# bar diff --git a/tests/functional/lang/eval-fail-eol-2.err.exp b/tests/functional/lang/eval-fail-eol-2.err.exp new file mode 100644 index 000000000..ff13e2d55 --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-2.err.exp @@ -0,0 +1,6 @@ +error: undefined variable 'invalid' + at /pwd/lang/eval-fail-eol-2.nix:2:1: + 1| # foo + 2| invalid + | ^ + 3| # bar diff --git a/tests/functional/lang/eval-fail-eol-2.nix b/tests/functional/lang/eval-fail-eol-2.nix new file mode 100644 index 000000000..0cf92a425 --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-2.nix @@ -0,0 +1,2 @@ +# foo invalid +# bar diff --git a/tests/functional/lang/eval-fail-eol-3.err.exp b/tests/functional/lang/eval-fail-eol-3.err.exp new file mode 100644 index 000000000..ada3c5ecd --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-3.err.exp @@ -0,0 +1,6 @@ +error: undefined variable 'invalid' + at /pwd/lang/eval-fail-eol-3.nix:2:1: + 1| # foo + 2| invalid + | ^ + 3| # bar diff --git a/tests/functional/lang/eval-fail-eol-3.nix b/tests/functional/lang/eval-fail-eol-3.nix new file mode 100644 index 000000000..33422452d --- /dev/null +++ b/tests/functional/lang/eval-fail-eol-3.nix @@ -0,0 +1,3 @@ +# foo +invalid +# bar