|  | // 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 dart2js.io.source_file; | 
|  |  | 
|  | import 'dart:math'; | 
|  | import 'dart:convert' show UTF8; | 
|  | import 'dart:typed_data' show Uint8List; | 
|  |  | 
|  | import 'line_column_provider.dart'; | 
|  |  | 
|  | /** | 
|  | * 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 implements LineColumnProvider { | 
|  | /// The absolute URI of the source file. | 
|  | Uri get uri; | 
|  |  | 
|  | /// The name of the file. | 
|  | /// | 
|  | /// This is [uri], maybe relativized to a more human-readable form. | 
|  | String get filename => uri.toString(); | 
|  |  | 
|  | /** 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>], | 
|  | * terminated with a trailing 0 byte. | 
|  | */ | 
|  | List<int> slowUtf8ZeroTerminatedBytes(); | 
|  |  | 
|  | /** | 
|  | * 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]; | 
|  | } | 
|  |  | 
|  | /// Returns the offset for 0-based [line] and [column] numbers. | 
|  | int getOffset(int line, int column) => lineStarts[line] + column; | 
|  |  | 
|  | String slowSubstring(int start, int end); | 
|  |  | 
|  | /** | 
|  | * Create a pretty string representation for [message] from a character | 
|  | * range `[start, end]` in this file. | 
|  | * | 
|  | * If [includeSourceLine] is `true` the first source line code line that | 
|  | * contains the range will be included as well as marker characters ('^') | 
|  | * underlining the range. | 
|  | * | 
|  | * Use [colorize] to wrap source code text and marker characters in color | 
|  | * escape codes. | 
|  | */ | 
|  | String getLocationMessage(String message, int start, int end, | 
|  | {bool includeSourceLine: true, | 
|  | String colorize(String text)}) { | 
|  | if (colorize == null) { | 
|  | colorize = (text) => text; | 
|  | } | 
|  | var line = getLine(start); | 
|  | var column = getColumn(line, start); | 
|  |  | 
|  | var buf = new StringBuffer('${filename}:'); | 
|  | if (start != end || start != 0) { | 
|  | // Line/column info is relevant. | 
|  | buf.write('${line + 1}:${column + 1}:'); | 
|  | } | 
|  | buf.write('\n$message\n'); | 
|  |  | 
|  | if (start != end && includeSourceLine) { | 
|  | String textLine = getLineText(line); | 
|  |  | 
|  | int toColumn = min(column + (end-start), textLine.length); | 
|  | buf.write(textLine.substring(0, column)); | 
|  | buf.write(colorize(textLine.substring(column, toColumn))); | 
|  | buf.write(textLine.substring(toColumn)); | 
|  |  | 
|  | int i = 0; | 
|  | for (; i < column; i++) { | 
|  | buf.write(' '); | 
|  | } | 
|  |  | 
|  | for (; i < toColumn; i++) { | 
|  | buf.write(colorize('^')); | 
|  | } | 
|  | } | 
|  |  | 
|  | return buf.toString(); | 
|  | } | 
|  |  | 
|  | int get lines => lineStarts.length - 1; | 
|  |  | 
|  | /// Returns the text of line  at the 0-based [index] within this source file. | 
|  | String getLineText(int index) { | 
|  | // +1 for 0-indexing, +1 again to avoid the last line of the file | 
|  | if ((index + 2) < lineStarts.length) { | 
|  | return slowSubstring(lineStarts[index], lineStarts[index+1]); | 
|  | } else if ((index + 1) < lineStarts.length) { | 
|  | return '${slowSubstring(lineStarts[index], length)}\n'; | 
|  | } else { | 
|  | throw new ArgumentError("Line index $index is out of bounds."); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | List<int> _zeroTerminateIfNecessary(List<int> bytes) { | 
|  | if (bytes.length > 0 && bytes.last == 0) return bytes; | 
|  | List<int> result = new Uint8List(bytes.length + 1); | 
|  | result.setRange(0, bytes.length, bytes); | 
|  | result[result.length - 1] = 0; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | class Utf8BytesSourceFile extends SourceFile { | 
|  | final Uri uri; | 
|  |  | 
|  | /** The UTF-8 encoded content of the source file. */ | 
|  | final List<int> zeroTerminatedContent; | 
|  |  | 
|  | /** | 
|  | * Creates a Utf8BytesSourceFile. | 
|  | * | 
|  | * If possible, the given [content] should be zero-terminated. If it isn't, | 
|  | * the constructor clones the content and adds a trailing 0. | 
|  | */ | 
|  | Utf8BytesSourceFile(this.uri, List<int> content) | 
|  | : this.zeroTerminatedContent = _zeroTerminateIfNecessary(content); | 
|  |  | 
|  | String slowText() { | 
|  | // Don't convert the trailing zero byte. | 
|  | return UTF8.decoder.convert( | 
|  | zeroTerminatedContent, 0, zeroTerminatedContent.length - 1); | 
|  | } | 
|  |  | 
|  | List<int> slowUtf8ZeroTerminatedBytes() => zeroTerminatedContent; | 
|  |  | 
|  | 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. | 
|  | lengthCache = slowText().length; | 
|  | } | 
|  | return lengthCache; | 
|  | } | 
|  | set length(int v) => lengthCache = v; | 
|  | int lengthCache = -1; | 
|  | } | 
|  |  | 
|  | class CachingUtf8BytesSourceFile extends Utf8BytesSourceFile { | 
|  | String cachedText; | 
|  | final String filename; | 
|  |  | 
|  | CachingUtf8BytesSourceFile(Uri uri, this.filename, List<int> content) | 
|  | : super(uri, content); | 
|  |  | 
|  | String slowText() { | 
|  | if (cachedText == null) { | 
|  | cachedText = super.slowText(); | 
|  | } | 
|  | return cachedText; | 
|  | } | 
|  | } | 
|  |  | 
|  | class StringSourceFile extends SourceFile { | 
|  | final Uri uri; | 
|  | final String filename; | 
|  | final String text; | 
|  |  | 
|  | StringSourceFile(this.uri, this.filename, this.text); | 
|  |  | 
|  | StringSourceFile.fromUri(Uri uri, String text) | 
|  | : this(uri, uri.toString(), text); | 
|  |  | 
|  | StringSourceFile.fromName(String filename, String text) | 
|  | : this(new Uri(path: filename), filename, text); | 
|  |  | 
|  | int get length => text.length; | 
|  | set length(int v) { } | 
|  |  | 
|  | String slowText() => text; | 
|  |  | 
|  | List<int> slowUtf8ZeroTerminatedBytes() { | 
|  | return _zeroTerminateIfNecessary(UTF8.encode(text)); | 
|  | } | 
|  |  | 
|  | String slowSubstring(int start, int end) => text.substring(start, end); | 
|  | } |