| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library source_writer; | 5 library source_writer; |
| 6 | 6 |
| 7 | 7 import 'dart:math' as math; |
| 8 | 8 |
| 9 class Line { | 9 class Line { |
| 10 | 10 |
| 11 final List<LineToken> tokens = <LineToken>[]; | 11 final List<LineToken> tokens = <LineToken>[]; |
| 12 final bool useTabs; | 12 final bool useTabs; |
| 13 final int spacesPerIndent; | 13 final int spacesPerIndent; |
| 14 final int indentLevel; | 14 final int indentLevel; |
| 15 final LinePrinter printer; | 15 final LinePrinter printer; |
| 16 | 16 |
| 17 Line({this.indentLevel: 0, this.useTabs: false, this.spacesPerIndent: 2, | 17 Line({this.indentLevel: 0, this.useTabs: false, this.spacesPerIndent: 2, |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 77 SimpleLineBreaker(this.maxLength, [this.indenter]) { | 77 SimpleLineBreaker(this.maxLength, [this.indenter]) { |
| 78 if (indenter == null) { | 78 if (indenter == null) { |
| 79 indenter = NO_OP_INDENTER; | 79 indenter = NO_OP_INDENTER; |
| 80 } | 80 } |
| 81 } | 81 } |
| 82 | 82 |
| 83 String printLine(Line line) { | 83 String printLine(Line line) { |
| 84 var buf = new StringBuffer(); | 84 var buf = new StringBuffer(); |
| 85 var chunks = breakLine(line); | 85 var chunks = breakLine(line); |
| 86 for (var i = 0; i < chunks.length; ++i) { | 86 for (var i = 0; i < chunks.length; ++i) { |
| 87 var chunk = chunks[i]; |
| 87 if (i > 0) { | 88 if (i > 0) { |
| 88 buf.write(indent(chunks[i], line.indentLevel)); | 89 buf.write(indent(chunk, chunk.indent)); |
| 89 } else { | 90 } else { |
| 90 buf.write(chunks[i]); | 91 buf.write(chunk); |
| 91 } | 92 } |
| 92 } | 93 } |
| 93 return buf.toString(); | 94 return buf.toString(); |
| 94 } | 95 } |
| 95 | 96 |
| 96 String indent(Chunk chunk, int level) => | 97 String indent(Chunk chunk, int level) { |
| 97 '\n' + indenter(level + 2) + chunk.toString(); | 98 return '\n' + indenter(level) + chunk.toString(); |
| 99 } |
| 98 | 100 |
| 99 List<Chunk> breakLine(Line line) { | 101 List<Chunk> breakLine(Line line) { |
| 100 | 102 List<LineToken> tokens = preprocess(line.tokens); |
| 101 var tokens = preprocess(line.tokens); | 103 List<Chunk> chunks = <Chunk>[new Chunk(line.indentLevel, maxLength, tokens)]
; |
| 102 | 104 while (true) { |
| 103 var chunks = <Chunk>[]; | 105 List<Chunk> newChunks = <Chunk>[]; |
| 104 | 106 bool hasChanges = false; |
| 105 // The current unbroken line | 107 for (Chunk chunk in chunks) { |
| 106 var current = new Chunk(maxLength: maxLength); | 108 tokens = chunk.tokens; |
| 107 | 109 if (chunk.length > maxLength) { |
| 108 // A tentative working chunk that will either start a new line or get | 110 if (chunk.hasAnySpace()) { |
| 109 // absorbed into 'current' | 111 int weight = chunk.findMinSpaceWeight(); |
| 110 var work = new Chunk(maxLength: maxLength); | 112 int newIndent = chunk.indent; |
| 111 | 113 if (weight == DEFAULT_SPACE_WEIGHT) { |
| 112 tokens.forEach((tok) { | 114 int start = 0; |
| 113 | 115 int length = 0; |
| 114 if (goodStart(tok, work)) { | 116 for (int i = 0; i < tokens.length; i++) { |
| 115 if (current.fits(work)) { | 117 LineToken token = tokens[i]; |
| 116 current.add(work); | 118 if (token is SpaceToken && token.breakWeight == weight |
| 119 && i < tokens.length - 1) { |
| 120 LineToken nextToken = tokens[i + 1]; |
| 121 if (length + token.length + nextToken.length > maxLength) { |
| 122 newChunks.add(chunk.subChunk(newIndent, start, i)); |
| 123 newIndent = chunk.indent + 2; |
| 124 start = i + 1; |
| 125 length = 0; |
| 126 continue; |
| 127 } |
| 128 } |
| 129 length += token.length; |
| 130 } |
| 131 if (start < tokens.length) { |
| 132 newChunks.add(chunk.subChunk(newIndent, start)); |
| 133 } |
| 134 } else { |
| 135 List<LineToken> part = []; |
| 136 int start = 0; |
| 137 for (int i = 0; i < tokens.length; i++) { |
| 138 LineToken token = tokens[i]; |
| 139 if (token is SpaceToken && token.breakWeight == weight) { |
| 140 newChunks.add(chunk.subChunk(newIndent, start, i)); |
| 141 newIndent = chunk.indent + 2; |
| 142 start = i + 1; |
| 143 } |
| 144 } |
| 145 if (start < tokens.length) { |
| 146 newChunks.add(chunk.subChunk(newIndent, start)); |
| 147 } |
| 148 } |
| 149 } else { |
| 150 newChunks.add(chunk); |
| 151 } |
| 117 } else { | 152 } else { |
| 118 if (current.length > 0) { | 153 newChunks.add(chunk); |
| 119 chunks.add(current); | |
| 120 } | |
| 121 current = work; | |
| 122 } | 154 } |
| 123 work = new Chunk(start: tok, maxLength: maxLength - current.length); | 155 if (newChunks.length > chunks.length) { |
| 124 } else { | 156 hasChanges = true; |
| 125 if (work.fits(tok)) { | |
| 126 work.add(tok); | |
| 127 } else { | |
| 128 if (!isAllWhitespace(work) || isLineStart(current)) { | |
| 129 current.add(work); | |
| 130 } else if (current.length > 0) { | |
| 131 chunks.add(current); | |
| 132 current = new Chunk(maxLength: maxLength); | |
| 133 } | |
| 134 work = new Chunk(maxLength: maxLength); | |
| 135 work.add(tok); | |
| 136 } | 157 } |
| 137 } | 158 } |
| 138 | 159 if (!hasChanges) { |
| 139 }); | 160 break; |
| 140 | 161 } |
| 141 current.add(work); | 162 chunks = newChunks; |
| 142 if (current.length > 0) { | |
| 143 chunks.add(current); | |
| 144 } | 163 } |
| 145 return chunks; | 164 return chunks; |
| 146 } | 165 } |
| 147 | 166 |
| 148 static List<LineToken> preprocess(List<LineToken> tok) { | 167 static List<LineToken> preprocess(List<LineToken> tok) { |
| 149 | 168 |
| 150 var tokens = <LineToken>[]; | 169 var tokens = <LineToken>[]; |
| 151 var curr; | 170 var curr; |
| 152 | 171 |
| 153 tok.forEach((token){ | 172 tok.forEach((token) { |
| 154 if (token is! SpaceToken) { | 173 if (token is! SpaceToken) { |
| 155 if (curr == null) { | 174 if (curr == null) { |
| 156 curr = token; | 175 curr = token; |
| 157 } else { | 176 } else { |
| 158 curr = merge(curr, token); | 177 curr = merge(curr, token); |
| 159 } | 178 } |
| 160 } else { | 179 } else { |
| 161 if (isNonbreaking(token)) { | 180 if (isNonbreaking(token)) { |
| 162 curr = merge(curr, token); | 181 curr = merge(curr, token); |
| 163 } else { | 182 } else { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 175 } | 194 } |
| 176 | 195 |
| 177 return tokens; | 196 return tokens; |
| 178 } | 197 } |
| 179 | 198 |
| 180 static bool isNonbreaking(SpaceToken token) => | 199 static bool isNonbreaking(SpaceToken token) => |
| 181 token.breakWeight == UNBREAKABLE_SPACE_WEIGHT; | 200 token.breakWeight == UNBREAKABLE_SPACE_WEIGHT; |
| 182 | 201 |
| 183 static LineToken merge(LineToken first, LineToken second) => | 202 static LineToken merge(LineToken first, LineToken second) => |
| 184 new LineToken(first.value + second.value); | 203 new LineToken(first.value + second.value); |
| 185 | |
| 186 bool isAllWhitespace(Chunk chunk) => isWhitespace(chunk.buffer.toString()); | |
| 187 | |
| 188 bool isLineStart(chunk) => chunk.length == 0 && chunk.start == LINE_START; | |
| 189 | |
| 190 /// Test whether this token is a good start for a new working chunk | |
| 191 bool goodStart(LineToken tok, Chunk workingChunk) => | |
| 192 tok is SpaceToken && tok.breakWeight >= workingChunk.start.breakWeight; | |
| 193 | |
| 194 } | 204 } |
| 195 | 205 |
| 196 /// Test if this [string] contains only whitespace characters | 206 /// Test if this [string] contains only whitespace characters |
| 197 bool isWhitespace(String string) => string.codeUnits.every( | 207 bool isWhitespace(String string) => string.codeUnits.every( |
| 198 (c) => c == 0x09 || c == 0x20 || c == 0x0A || c == 0x0D); | 208 (c) => c == 0x09 || c == 0x20 || c == 0x0A || c == 0x0D); |
| 199 | 209 |
| 200 /// Special token indicating a line start | 210 /// Special token indicating a line start |
| 201 final LINE_START = new SpaceToken(0); | 211 final LINE_START = new SpaceToken(0); |
| 202 | 212 |
| 203 const DEFAULT_SPACE_WEIGHT = 0; | 213 const DEFAULT_SPACE_WEIGHT = UNBREAKABLE_SPACE_WEIGHT - 1; |
| 204 const UNBREAKABLE_SPACE_WEIGHT = -1; | 214 const UNBREAKABLE_SPACE_WEIGHT = 1000; |
| 205 | 215 |
| 206 /// Simple non-breaking printer | 216 /// Simple non-breaking printer |
| 207 class SimpleLinePrinter extends LinePrinter { | 217 class SimpleLinePrinter extends LinePrinter { |
| 208 | 218 |
| 209 const SimpleLinePrinter(); | 219 const SimpleLinePrinter(); |
| 210 | 220 |
| 211 String printLine(Line line) { | 221 String printLine(Line line) { |
| 212 var buffer = new StringBuffer(); | 222 var buffer = new StringBuffer(); |
| 213 line.tokens.forEach((tok) => buffer.write(tok.toString())); | 223 line.tokens.forEach((tok) => buffer.write(tok.toString())); |
| 214 return buffer.toString(); | 224 return buffer.toString(); |
| 215 } | 225 } |
| 216 | 226 |
| 217 } | 227 } |
| 218 | 228 |
| 219 | 229 |
| 220 /// Describes a piece of text in a [Line]. | 230 /// Describes a piece of text in a [Line]. |
| 221 abstract class LineText { | 231 abstract class LineText { |
| 222 int get length; | 232 int get length; |
| 223 void addTo(Chunk chunk); | |
| 224 } | 233 } |
| 225 | 234 |
| 226 | 235 |
| 227 /// A working piece of text used in calculating line breaks | 236 /// A working piece of text used in calculating line breaks |
| 228 class Chunk implements LineText { | 237 class Chunk { |
| 238 final int indent; |
| 239 final int maxLength; |
| 240 final List<LineToken> tokens = <LineToken>[]; |
| 229 | 241 |
| 230 final StringBuffer buffer = new StringBuffer(); | 242 Chunk(this.indent, this.maxLength, [List<LineToken> tokens]) { |
| 231 | 243 this.tokens.addAll(tokens); |
| 232 int maxLength; | |
| 233 SpaceToken start; | |
| 234 | |
| 235 Chunk({this.start, this.maxLength}) { | |
| 236 if (start == null) { | |
| 237 start = LINE_START; | |
| 238 } | |
| 239 } | 244 } |
| 240 | 245 |
| 241 bool fits(LineText text) => length + text.length <= maxLength; | 246 int get length => tokens.fold(0, (len, token) => len + token.length); |
| 242 | 247 |
| 243 int get length => start.value.length + buffer.length; | 248 bool fits(LineToken a, LineToken b) { |
| 244 | 249 return length + a.length + a.length <= maxLength; |
| 245 void add(LineText text) { | |
| 246 text.addTo(this); | |
| 247 } | 250 } |
| 248 | 251 |
| 249 String toString() => buffer.toString(); | 252 void add(LineToken token) { |
| 253 tokens.add(token); |
| 254 } |
| 250 | 255 |
| 251 void addTo(Chunk chunk) { | 256 bool hasAnySpace() { |
| 252 chunk.buffer.write(start.value); | 257 return tokens.any((token) => token is SpaceToken); |
| 253 chunk.buffer.write(buffer.toString()); | |
| 254 } | 258 } |
| 259 |
| 260 int findMinSpaceWeight() { |
| 261 int minWeight = UNBREAKABLE_SPACE_WEIGHT; |
| 262 for (var token in tokens) { |
| 263 if (token is SpaceToken) { |
| 264 minWeight = math.min(minWeight, token.breakWeight); |
| 265 } |
| 266 } |
| 267 return minWeight; |
| 268 } |
| 269 |
| 270 Chunk subChunk(int indentLevel, int start, [int end]) { |
| 271 List<LineToken> subTokens = tokens.sublist(start, end); |
| 272 return new Chunk(indentLevel, maxLength, subTokens); |
| 273 } |
| 274 |
| 275 String toString() => tokens.join(); |
| 255 } | 276 } |
| 256 | 277 |
| 257 | 278 |
| 258 class LineToken implements LineText { | 279 class LineToken implements LineText { |
| 259 | 280 |
| 260 final String value; | 281 final String value; |
| 261 | 282 |
| 262 LineToken(this.value); | 283 LineToken(this.value); |
| 263 | 284 |
| 264 String toString() => value; | 285 String toString() => value; |
| 265 | 286 |
| 266 int get length => lengthLessNewlines(value); | 287 int get length => lengthLessNewlines(value); |
| 267 | 288 |
| 268 void addTo(Chunk chunk) { | |
| 269 chunk.buffer.write(value); | |
| 270 } | |
| 271 | |
| 272 int lengthLessNewlines(String str) => | 289 int lengthLessNewlines(String str) => |
| 273 str.endsWith('\n') ? str.length - 1 : str.length; | 290 str.endsWith('\n') ? str.length - 1 : str.length; |
| 274 | 291 |
| 275 } | 292 } |
| 276 | 293 |
| 277 | 294 |
| 278 class SpaceToken extends LineToken { | 295 class SpaceToken extends LineToken { |
| 279 | 296 |
| 280 final int breakWeight; | 297 final int breakWeight; |
| 281 | 298 |
| (...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 446 | 463 |
| 447 String getTabs(int n) => n < TABS.length ? TABS[n] : repeat('\t', n); | 464 String getTabs(int n) => n < TABS.length ? TABS[n] : repeat('\t', n); |
| 448 | 465 |
| 449 String repeat(String ch, int times) { | 466 String repeat(String ch, int times) { |
| 450 var sb = new StringBuffer(); | 467 var sb = new StringBuffer(); |
| 451 for (var i = 0; i < times; ++i) { | 468 for (var i = 0; i < times; ++i) { |
| 452 sb.write(ch); | 469 sb.write(ch); |
| 453 } | 470 } |
| 454 return sb.toString(); | 471 return sb.toString(); |
| 455 } | 472 } |
| OLD | NEW |