content: // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
  // for details. All rights reserved. Use of this source code is governed by a
  // BSD-style license that can be found in the LICENSE file.
  
  library source_file;
  
  import 'dart:math';
  import 'dart:convert' show UTF8;
  
  /**
   * Represents a file of source code. The content can be either a [String] or
   * a UTF-8 encoded [List<int>] of bytes.
   */
  abstract class SourceFile {
  
    /** The name of the file. */
    final String filename;
  
    SourceFile(this.filename);
  
    /** The text content of the file represented as a String. */
    String slowText();
  
    /** The content of the file represented as a UTF-8 encoded [List<int>]. */
    List<int> slowUtf8Bytes();
  
    /**
     * The length of the string representation of this source file, i.e.,
     * equivalent to [:slowText().length:], but faster.
     */
    int get length;
  
    /**
     * Sets the string length of this source file. For source files based on UTF-8
     * byte arrays, the string length is computed and assigned by the scanner.
     */
    set length(int v);
  
    /**
     * A map from line numbers to offsets in the string text representation of
     * this source file.
     */
    List<int> get lineStarts {
      if (lineStartsCache == null) {
        // When reporting errors during scanning, the line numbers are not yet
        // available and need to be computed using this slow path.
        lineStartsCache = lineStartsFromString(slowText());
      }
      return lineStartsCache;
    }
  
    /**
     * Sets the line numbers map for this source file. This map is computed and
     * assigned by the scanner, avoiding a separate traversal of the source file.
     *
     * The map contains one additional entry at the end of the file, as if the
     * source file had one more empty line at the end. This simplifies the binary
     * search in [getLine].
     */
    set lineStarts(List<int> v) => lineStartsCache = v;
  
    List<int> lineStartsCache;
  
    List<int> lineStartsFromString(String text) {
      var starts = [0];
      var index = 0;
      while (index < text.length) {
        index = text.indexOf('\n', index) + 1;
        if (index <= 0) break;
        starts.add(index);
      }
      starts.add(text.length + 1); // One additional line start at the end.
      return starts;
    }
  
    /**
     * Returns the line number for the offset [position] in the string
     * representation of this source file.
     */
    int getLine(int position) {
      List<int> starts = lineStarts;
      if (position < 0 || starts.last <= position) {
        throw 'bad position #$position in file $filename with '
              'length ${length}.';
      }
      int first = 0;
      int count = starts.length;
      while (count > 1) {
        int step = count ~/ 2;
        int middle = first + step;
        int lineStart = starts[middle];
        if (position < lineStart) {
          count = step;
        } else {
          first = middle;
          count -= step;
        }
      }
      return first;
    }
  
    /**
     * Returns the column number for the offset [position] in the string
     * representation of this source file.
     */
    int getColumn(int line, int position) {
      return position - lineStarts[line];
    }
  
    String slowSubstring(int start, int end);
  
    /**
     * Create a pretty string representation from a character position
     * in the file.
     */
    String getLocationMessage(String message, int start, int end,
                              bool includeText, String color(String x)) {
      var line = getLine(start);
      var column = getColumn(line, start);
  
      var buf = new StringBuffer(
          '${filename}:${line + 1}:${column + 1}: $message');
      if (includeText) {
        buf.write('\n');
        String textLine;
        // +1 for 0-indexing, +1 again to avoid the last line of the file
        if ((line + 2) < lineStarts.length) {
          textLine = slowSubstring(lineStarts[line], lineStarts[line+1]);
        } else {
          textLine = '${slowSubstring(lineStarts[line], length)}\n';
        }
  
        int toColumn = min(column + (end-start), textLine.length);
        buf.write(textLine.substring(0, column));
        buf.write(color(textLine.substring(column, toColumn)));
        buf.write(textLine.substring(toColumn));
  
        int i = 0;
        for (; i < column; i++) {
          buf.write(' ');
        }
  
        for (; i < toColumn; i++) {
          buf.write(color('^'));
        }
      }
  
      return buf.toString();
    }
  }
  
  class Utf8BytesSourceFile extends SourceFile {
  
    /** The UTF-8 encoded content of the source file. */
    final List<int> content;
  
    Utf8BytesSourceFile(String filename, this.content) : super(filename);
  
    String slowText() => UTF8.decode(content);
  
    List<int> slowUtf8Bytes() => content;
  
    String slowSubstring(int start, int end) {
      // TODO(lry): to make this faster, the scanner could record the UTF-8 slack
      // for all positions of the source text. We could use [:content.sublist:].
      return slowText().substring(start, end);
    }
  
    int get length {
      if (lengthCache == -1) {
        // During scanning the length is not yet assigned, so we use a slow path.
        length = slowText().length;
      }
      return lengthCache;
    }
    set length(int v) => lengthCache = v;
    int lengthCache = -1;
  }
  
  class StringSourceFile extends SourceFile {
  
    final String text;
  
    StringSourceFile(String filename, this.text) : super(filename);
  
    int get length => text.length;
    set length(int v) { }
  
    String slowText() => text;
  
    List<int> slowUtf8Bytes() => UTF8.encode(text);
  
    String slowSubstring(int start, int end) => text.substring(start, end);
  }
  