OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 /// Contains a code printer that generates code by recording the source maps. |
| 6 library source_maps.printer; |
| 7 |
| 8 import 'package:source_span/source_span.dart'; |
| 9 |
| 10 import 'builder.dart'; |
| 11 import 'src/source_map_span.dart'; |
| 12 |
| 13 const int _LF = 10; |
| 14 const int _CR = 13; |
| 15 |
| 16 /// A simple printer that keeps track of offset locations and records source |
| 17 /// maps locations. |
| 18 class Printer { |
| 19 final String filename; |
| 20 final StringBuffer _buff = new StringBuffer(); |
| 21 final SourceMapBuilder _maps = new SourceMapBuilder(); |
| 22 String get text => _buff.toString(); |
| 23 String get map => _maps.toJson(filename); |
| 24 |
| 25 /// Current source location mapping. |
| 26 SourceLocation _loc; |
| 27 |
| 28 /// Current line in the buffer; |
| 29 int _line = 0; |
| 30 |
| 31 /// Current column in the buffer. |
| 32 int _column = 0; |
| 33 |
| 34 Printer(this.filename); |
| 35 |
| 36 /// Add [str] contents to the output, tracking new lines to track correct |
| 37 /// positions for span locations. When [projectMarks] is true, this method |
| 38 /// adds a source map location on each new line, projecting that every new |
| 39 /// line in the target file (printed here) corresponds to a new line in the |
| 40 /// source file. |
| 41 void add(String str, {projectMarks: false}) { |
| 42 var chars = str.runes.toList(); |
| 43 var length = chars.length; |
| 44 for (int i = 0; i < length; i++) { |
| 45 var c = chars[i]; |
| 46 if (c == _LF || (c == _CR && (i + 1 == length || chars[i + 1] != _LF))) { |
| 47 // Return not followed by line-feed is treated as a new line. |
| 48 _line++; |
| 49 _column = 0; |
| 50 if (projectMarks && _loc != null) { |
| 51 if (_loc is FileLocation) { |
| 52 var file = (_loc as FileLocation).file; |
| 53 mark(file.location(file.getOffset(_loc.line + 1))); |
| 54 } else { |
| 55 mark(new SourceLocation(0, |
| 56 sourceUrl: _loc.sourceUrl, line: _loc.line + 1, column: 0)); |
| 57 } |
| 58 } |
| 59 } else { |
| 60 _column++; |
| 61 } |
| 62 } |
| 63 _buff.write(str); |
| 64 } |
| 65 |
| 66 |
| 67 /// Append a [total] number of spaces in the target file. Typically used for |
| 68 /// formatting indentation. |
| 69 void addSpaces(int total) { |
| 70 for (int i = 0; i < total; i++) _buff.write(' '); |
| 71 _column += total; |
| 72 } |
| 73 |
| 74 /// Marks that the current point in the target file corresponds to the [mark] |
| 75 /// in the source file, which can be either a [SourceLocation] or a |
| 76 /// [SourceSpan]. When the mark is a [SourceMapSpan] with `isIdentifier` set, |
| 77 /// this also records the name of the identifier in the source map |
| 78 /// information. |
| 79 void mark(mark) { |
| 80 var loc; |
| 81 var identifier = null; |
| 82 if (mark is SourceLocation) { |
| 83 loc = mark; |
| 84 } else if (mark is SourceSpan) { |
| 85 loc = mark.start; |
| 86 if (mark is SourceMapSpan && mark.isIdentifier) identifier = mark.text; |
| 87 } |
| 88 _maps.addLocation( |
| 89 loc, |
| 90 new SourceLocation(_buff.length, line: _line, column: _column), |
| 91 identifier); |
| 92 _loc = loc; |
| 93 } |
| 94 } |
| 95 |
| 96 /// A more advanced printer that keeps track of offset locations to record |
| 97 /// source maps, but additionally allows nesting of different kind of items, |
| 98 /// including [NestedPrinter]s, and it let's you automatically indent text. |
| 99 /// |
| 100 /// This class is especially useful when doing code generation, where different |
| 101 /// peices of the code are generated independently on separate printers, and are |
| 102 /// finally put together in the end. |
| 103 class NestedPrinter implements NestedItem { |
| 104 |
| 105 /// Items recoded by this printer, which can be [String] literals, |
| 106 /// [NestedItem]s, and source map information like [SourceLocation] and |
| 107 /// [SourceSpan]. |
| 108 List _items = []; |
| 109 |
| 110 /// Internal buffer to merge consecutive strings added to this printer. |
| 111 StringBuffer _buff; |
| 112 |
| 113 /// Current indentation, which can be updated from outside this class. |
| 114 int indent; |
| 115 |
| 116 /// Item used to indicate that the following item is copied from the original |
| 117 /// source code, and hence we should preserve source-maps on every new line. |
| 118 static final _ORIGINAL = new Object(); |
| 119 |
| 120 NestedPrinter([this.indent = 0]); |
| 121 |
| 122 /// Adds [object] to this printer. [object] can be a [String], |
| 123 /// [NestedPrinter], or anything implementing [NestedItem]. If [object] is a |
| 124 /// [String], the value is appended directly, without doing any formatting |
| 125 /// changes. If you wish to add a line of code with automatic indentation, use |
| 126 /// [addLine] instead. [NestedPrinter]s and [NestedItem]s are not processed |
| 127 /// until [build] gets called later on. We ensure that [build] emits every |
| 128 /// object in the order that they were added to this printer. |
| 129 /// |
| 130 /// The [location] and [span] parameters indicate the corresponding source map |
| 131 /// location of [object] in the original input. Only one, [location] or |
| 132 /// [span], should be provided at a time. |
| 133 /// |
| 134 /// Indicate [isOriginal] when [object] is copied directly from the user code. |
| 135 /// Setting [isOriginal] will make this printer propagate source map locations |
| 136 /// on every line-break. |
| 137 void add(object, {SourceLocation location, SourceSpan span, |
| 138 bool isOriginal: false}) { |
| 139 if (object is! String || location != null || span != null || isOriginal) { |
| 140 _flush(); |
| 141 assert(location == null || span == null); |
| 142 if (location != null) _items.add(location); |
| 143 if (span != null) _items.add(span); |
| 144 if (isOriginal) _items.add(_ORIGINAL); |
| 145 } |
| 146 |
| 147 if (object is String) { |
| 148 _appendString(object); |
| 149 } else { |
| 150 _items.add(object); |
| 151 } |
| 152 } |
| 153 |
| 154 /// Append `2 * indent` spaces to this printer. |
| 155 void insertIndent() => _indent(indent); |
| 156 |
| 157 /// Add a [line], autoindenting to the current value of [indent]. Note, |
| 158 /// indentation is not inferred from the contents added to this printer. If a |
| 159 /// line starts or ends an indentation block, you need to also update [indent] |
| 160 /// accordingly. Also, indentation is not adapted for nested printers. If |
| 161 /// you add a [NestedPrinter] to this printer, its indentation is set |
| 162 /// separately and will not include any the indentation set here. |
| 163 /// |
| 164 /// The [location] and [span] parameters indicate the corresponding source map |
| 165 /// location of [object] in the original input. Only one, [location] or |
| 166 /// [span], should be provided at a time. |
| 167 void addLine(String line, {SourceLocation location, SourceSpan span}) { |
| 168 if (location != null || span != null) { |
| 169 _flush(); |
| 170 assert(location == null || span == null); |
| 171 if (location != null) _items.add(location); |
| 172 if (span != null) _items.add(span); |
| 173 } |
| 174 if (line == null) return; |
| 175 if (line != '') { |
| 176 // We don't indent empty lines. |
| 177 _indent(indent); |
| 178 _appendString(line); |
| 179 } |
| 180 _appendString('\n'); |
| 181 } |
| 182 |
| 183 /// Appends a string merging it with any previous strings, if possible. |
| 184 void _appendString(String s) { |
| 185 if (_buff == null) _buff = new StringBuffer(); |
| 186 _buff.write(s); |
| 187 } |
| 188 |
| 189 /// Adds all of the current [_buff] contents as a string item. |
| 190 void _flush() { |
| 191 if (_buff != null) { |
| 192 _items.add(_buff.toString()); |
| 193 _buff = null; |
| 194 } |
| 195 } |
| 196 |
| 197 void _indent(int indent) { |
| 198 for (int i = 0; i < indent; i++) _appendString(' '); |
| 199 } |
| 200 |
| 201 /// Returns a string representation of all the contents appended to this |
| 202 /// printer, including source map location tokens. |
| 203 String toString() { |
| 204 _flush(); |
| 205 return (new StringBuffer()..writeAll(_items)).toString(); |
| 206 } |
| 207 |
| 208 /// [Printer] used during the last call to [build], if any. |
| 209 Printer printer; |
| 210 |
| 211 /// Returns the text produced after calling [build]. |
| 212 String get text => printer.text; |
| 213 |
| 214 /// Returns the source-map information produced after calling [build]. |
| 215 String get map => printer.map; |
| 216 |
| 217 /// Builds the output of this printer and source map information. After |
| 218 /// calling this function, you can use [text] and [map] to retrieve the |
| 219 /// geenrated code and source map information, respectively. |
| 220 void build(String filename) { |
| 221 writeTo(printer = new Printer(filename)); |
| 222 } |
| 223 |
| 224 /// Implements the [NestedItem] interface. |
| 225 void writeTo(Printer printer) { |
| 226 _flush(); |
| 227 bool propagate = false; |
| 228 for (var item in _items) { |
| 229 if (item is NestedItem) { |
| 230 item.writeTo(printer); |
| 231 } else if (item is String) { |
| 232 printer.add(item, projectMarks: propagate); |
| 233 propagate = false; |
| 234 } else if (item is SourceLocation || item is SourceSpan) { |
| 235 printer.mark(item); |
| 236 } else if (item == _ORIGINAL) { |
| 237 // we insert booleans when we are about to quote text that was copied |
| 238 // from the original source. In such case, we will propagate marks on |
| 239 // every new-line. |
| 240 propagate = true; |
| 241 } else { |
| 242 throw new UnsupportedError('Unknown item type: $item'); |
| 243 } |
| 244 } |
| 245 } |
| 246 } |
| 247 |
| 248 /// An item added to a [NestedPrinter]. |
| 249 abstract class NestedItem { |
| 250 /// Write the contents of this item into [printer]. |
| 251 void writeTo(Printer printer); |
| 252 } |
OLD | NEW |