| Index: tools/gn/err.cc
 | 
| diff --git a/tools/gn/err.cc b/tools/gn/err.cc
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..068ea6ddce4496a7c06699906d4feb8e3f3a747d
 | 
| --- /dev/null
 | 
| +++ b/tools/gn/err.cc
 | 
| @@ -0,0 +1,196 @@
 | 
| +// Copyright (c) 2013 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +#include "tools/gn/err.h"
 | 
| +
 | 
| +#include "base/strings/string_number_conversions.h"
 | 
| +#include "base/strings/string_util.h"
 | 
| +#include "tools/gn/filesystem_utils.h"
 | 
| +#include "tools/gn/input_file.h"
 | 
| +#include "tools/gn/parse_tree.h"
 | 
| +#include "tools/gn/standard_out.h"
 | 
| +#include "tools/gn/tokenizer.h"
 | 
| +#include "tools/gn/value.h"
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
| +std::string GetNthLine(const base::StringPiece& data, int n) {
 | 
| +  size_t line_off = Tokenizer::ByteOffsetOfNthLine(data, n);
 | 
| +  size_t end = line_off + 1;
 | 
| +  while (end < data.size() && !Tokenizer::IsNewline(data, end))
 | 
| +    end++;
 | 
| +  return data.substr(line_off, end - line_off).as_string();
 | 
| +}
 | 
| +
 | 
| +void FillRangeOnLine(const LocationRange& range, int line_number,
 | 
| +                     std::string* line) {
 | 
| +  // Only bother if the range's begin or end overlaps the line. If the entire
 | 
| +  // line is highlighted as a result of this range, it's not very helpful.
 | 
| +  if (range.begin().line_number() != line_number &&
 | 
| +      range.end().line_number() != line_number)
 | 
| +    return;
 | 
| +
 | 
| +  // Watch out, the char offsets in the location are 1-based, so we have to
 | 
| +  // subtract 1.
 | 
| +  int begin_char;
 | 
| +  if (range.begin().line_number() < line_number)
 | 
| +    begin_char = 0;
 | 
| +  else
 | 
| +    begin_char = range.begin().char_offset() - 1;
 | 
| +
 | 
| +  int end_char;
 | 
| +  if (range.end().line_number() > line_number)
 | 
| +    end_char = line->size();  // Ending is non-inclusive.
 | 
| +  else
 | 
| +    end_char = range.end().char_offset() - 1;
 | 
| +
 | 
| +  CHECK(end_char >= begin_char);
 | 
| +  CHECK(begin_char >= 0 && begin_char <= static_cast<int>(line->size()));
 | 
| +  CHECK(end_char >= 0 && end_char <= static_cast<int>(line->size()));
 | 
| +  for (int i = begin_char; i < end_char; i++)
 | 
| +    line->at(i) = '-';
 | 
| +}
 | 
| +
 | 
| +// The line length is used to clip the maximum length of the markers we'll
 | 
| +// make if the error spans more than one line (like unterminated literals).
 | 
| +void OutputHighlighedPosition(const Location& location,
 | 
| +                              const Err::RangeList& ranges,
 | 
| +                              size_t line_length) {
 | 
| +  // Make a buffer of the line in spaces.
 | 
| +  std::string highlight;
 | 
| +  highlight.resize(line_length);
 | 
| +  for (size_t i = 0; i < line_length; i++)
 | 
| +    highlight[i] = ' ';
 | 
| +
 | 
| +  // Highlight all the ranges on the line.
 | 
| +  for (size_t i = 0; i < ranges.size(); i++)
 | 
| +    FillRangeOnLine(ranges[i], location.line_number(), &highlight);
 | 
| +
 | 
| +  // Allow the marker to be one past the end of the line for marking the end.
 | 
| +  highlight.push_back(' ');
 | 
| +  CHECK(location.char_offset() - 1 >= 0 &&
 | 
| +        location.char_offset() - 1 < static_cast<int>(highlight.size()));
 | 
| +  highlight[location.char_offset() - 1] = '^';
 | 
| +
 | 
| +  // Trim unused spaces from end of line.
 | 
| +  while (!highlight.empty() && highlight[highlight.size() - 1] == ' ')
 | 
| +    highlight.resize(highlight.size() - 1);
 | 
| +
 | 
| +  highlight += "\n";
 | 
| +  OutputString(highlight, DECORATION_BLUE);
 | 
| +}
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +Err::Err() : has_error_(false) {
 | 
| +}
 | 
| +
 | 
| +Err::Err(const Location& location,
 | 
| +         const std::string& msg,
 | 
| +         const std::string& help)
 | 
| +    : has_error_(true),
 | 
| +      location_(location),
 | 
| +      message_(msg),
 | 
| +      help_text_(help) {
 | 
| +}
 | 
| +
 | 
| +Err::Err(const LocationRange& range,
 | 
| +         const std::string& msg,
 | 
| +         const std::string& help)
 | 
| +    : has_error_(true),
 | 
| +      location_(range.begin()),
 | 
| +      message_(msg),
 | 
| +      help_text_(help) {
 | 
| +  ranges_.push_back(range);
 | 
| +}
 | 
| +
 | 
| +Err::Err(const Token& token,
 | 
| +         const std::string& msg,
 | 
| +         const std::string& help)
 | 
| +    : has_error_(true),
 | 
| +      location_(token.location()),
 | 
| +      message_(msg),
 | 
| +      help_text_(help) {
 | 
| +  ranges_.push_back(token.range());
 | 
| +}
 | 
| +
 | 
| +Err::Err(const ParseNode* node,
 | 
| +         const std::string& msg,
 | 
| +         const std::string& help_text)
 | 
| +    : has_error_(true),
 | 
| +      message_(msg),
 | 
| +      help_text_(help_text) {
 | 
| +  // Node will be null in certain tests.
 | 
| +  if (node) {
 | 
| +    LocationRange range = node->GetRange();
 | 
| +    location_ = range.begin();
 | 
| +    ranges_.push_back(range);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +Err::Err(const Value& value,
 | 
| +         const std::string msg,
 | 
| +         const std::string& help_text)
 | 
| +    : has_error_(true),
 | 
| +      message_(msg),
 | 
| +      help_text_(help_text) {
 | 
| +  if (value.origin()) {
 | 
| +    LocationRange range = value.origin()->GetRange();
 | 
| +    location_ = range.begin();
 | 
| +    ranges_.push_back(range);
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +Err::~Err() {
 | 
| +}
 | 
| +
 | 
| +void Err::PrintToStdout() const {
 | 
| +  InternalPrintToStdout(false);
 | 
| +}
 | 
| +
 | 
| +void Err::AppendSubErr(const Err& err) {
 | 
| +  sub_errs_.push_back(err);
 | 
| +}
 | 
| +
 | 
| +void Err::InternalPrintToStdout(bool is_sub_err) const {
 | 
| +  DCHECK(has_error_);
 | 
| +
 | 
| +  if (!is_sub_err)
 | 
| +    OutputString("ERROR ", DECORATION_RED);
 | 
| +
 | 
| +  // File name and location.
 | 
| +  const InputFile* input_file = location_.file();
 | 
| +  std::string loc_str;
 | 
| +  if (input_file) {
 | 
| +    std::string path8;
 | 
| +    path8.assign(input_file->name().value());
 | 
| +
 | 
| +    if (is_sub_err)
 | 
| +      loc_str = "See ";
 | 
| +    else
 | 
| +      loc_str = "at ";
 | 
| +    loc_str += path8 + ": " +
 | 
| +        base::IntToString(location_.line_number()) + ":" +
 | 
| +        base::IntToString(location_.char_offset()) + ": ";
 | 
| +  }
 | 
| +  OutputString(loc_str + message_ + "\n");
 | 
| +
 | 
| +  // Quoted line.
 | 
| +  if (input_file) {
 | 
| +    std::string line = GetNthLine(input_file->contents(),
 | 
| +                                  location_.line_number());
 | 
| +    if (!ContainsOnlyWhitespaceASCII(line)) {
 | 
| +      OutputString(line + "\n", DECORATION_BOLD);
 | 
| +      OutputHighlighedPosition(location_, ranges_, line.size());
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  // Optional help text.
 | 
| +  if (!help_text_.empty())
 | 
| +    OutputString(help_text_ + "\n");
 | 
| +
 | 
| +  // Sub errors.
 | 
| +  for (size_t i = 0; i < sub_errs_.size(); i++)
 | 
| +    sub_errs_[i].InternalPrintToStdout(true);
 | 
| +}
 | 
| 
 |