216e8dfe85
Change-Id: I097253c0eb9ceb27322494194fbc6d899434807e Reviewed-on: https://dart-review.googlesource.com/61263 Reviewed-by: Sigmund Cherem <sigmund@google.com> Commit-Queue: Johnni Winther <johnniwinther@google.com>
208 lines
6.4 KiB
Dart
208 lines
6.4 KiB
Dart
// Copyright (c) 2016, 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.
|
|
|
|
const int _LF = 0x0A;
|
|
const int _CR = 0x0D;
|
|
|
|
const Pattern atBraceStart = '@{';
|
|
const Pattern braceEnd = '}';
|
|
|
|
final Pattern commentStart = new RegExp(r'/\*');
|
|
final Pattern commentEnd = new RegExp(r'\*/\s*');
|
|
|
|
class Annotation {
|
|
/// 1-based line number of the annotation.
|
|
final int lineNo;
|
|
|
|
/// 1-based column number of the annotation.
|
|
final int columnNo;
|
|
|
|
/// 0-based character offset of the annotation within the source text.
|
|
final int offset;
|
|
|
|
/// The text in the annotation.
|
|
final String text;
|
|
|
|
Annotation(this.lineNo, this.columnNo, this.offset, this.text);
|
|
|
|
String toString() =>
|
|
'Annotation(lineNo=$lineNo,columnNo=$columnNo,offset=$offset,text=$text)';
|
|
}
|
|
|
|
/// A source code text with annotated positions.
|
|
///
|
|
/// An [AnnotatedCode] can be created from a [String] of source code where
|
|
/// annotated positions are embedded, by default using the syntax `@{text}`.
|
|
/// For instance
|
|
///
|
|
/// main() {
|
|
/// @{foo-call}foo();
|
|
/// bar@{bar-args}();
|
|
/// }
|
|
///
|
|
/// the position of `foo` call will hold an annotation with text 'foo-call' and
|
|
/// the position of `bar` arguments will hold an annotation with text
|
|
/// 'bar-args'.
|
|
///
|
|
/// Annotation text cannot span multiple lines and cannot contain '}'.
|
|
class AnnotatedCode {
|
|
/// The source code without annotations.
|
|
final String sourceCode;
|
|
|
|
/// The annotations for the source code.
|
|
final List<Annotation> annotations;
|
|
|
|
List<int> _lineStarts;
|
|
|
|
AnnotatedCode(this.sourceCode, this.annotations);
|
|
|
|
AnnotatedCode.internal(this.sourceCode, this.annotations, this._lineStarts);
|
|
|
|
/// Creates an [AnnotatedCode] by processing [annotatedCode]. Annotation
|
|
/// delimited by [start] and [end] are converted into [Annotation]s and
|
|
/// removed from the [annotatedCode] to produce the source code.
|
|
factory AnnotatedCode.fromText(String annotatedCode,
|
|
[Pattern start = atBraceStart, Pattern end = braceEnd]) {
|
|
StringBuffer codeBuffer = new StringBuffer();
|
|
List<Annotation> annotations = <Annotation>[];
|
|
int index = 0;
|
|
int offset = 0;
|
|
int lineNo = 1;
|
|
int columnNo = 1;
|
|
List<int> lineStarts = <int>[];
|
|
lineStarts.add(offset);
|
|
while (index < annotatedCode.length) {
|
|
Match startMatch = start.matchAsPrefix(annotatedCode, index);
|
|
if (startMatch != null) {
|
|
int startIndex = startMatch.end;
|
|
Iterable<Match> endMatches =
|
|
end.allMatches(annotatedCode, startMatch.end);
|
|
if (!endMatches.isEmpty) {
|
|
Match endMatch = endMatches.first;
|
|
annotatedCode.indexOf(end, startIndex);
|
|
String text = annotatedCode.substring(startMatch.end, endMatch.start);
|
|
annotations.add(new Annotation(lineNo, columnNo, offset, text));
|
|
index = endMatch.end;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
int charCode = annotatedCode.codeUnitAt(index);
|
|
switch (charCode) {
|
|
case _LF:
|
|
codeBuffer.write('\n');
|
|
offset++;
|
|
lineStarts.add(offset);
|
|
lineNo++;
|
|
columnNo = 1;
|
|
break;
|
|
case _CR:
|
|
if (index + 1 < annotatedCode.length &&
|
|
annotatedCode.codeUnitAt(index + 1) == _LF) {
|
|
index++;
|
|
}
|
|
codeBuffer.write('\n');
|
|
offset++;
|
|
lineStarts.add(offset);
|
|
lineNo++;
|
|
columnNo = 1;
|
|
break;
|
|
default:
|
|
codeBuffer.writeCharCode(charCode);
|
|
offset++;
|
|
columnNo++;
|
|
}
|
|
index++;
|
|
}
|
|
lineStarts.add(offset);
|
|
return new AnnotatedCode.internal(
|
|
codeBuffer.toString(), annotations, lineStarts);
|
|
}
|
|
|
|
void _ensureLineStarts() {
|
|
if (_lineStarts == null) {
|
|
int index = 0;
|
|
int offset = 0;
|
|
_lineStarts = <int>[];
|
|
_lineStarts.add(offset);
|
|
while (index < sourceCode.length) {
|
|
int charCode = sourceCode.codeUnitAt(index);
|
|
switch (charCode) {
|
|
case _LF:
|
|
offset++;
|
|
_lineStarts.add(offset);
|
|
break;
|
|
case _CR:
|
|
if (index + 1 < sourceCode.length &&
|
|
sourceCode.codeUnitAt(index + 1) == _LF) {
|
|
index++;
|
|
}
|
|
offset++;
|
|
_lineStarts.add(offset);
|
|
break;
|
|
default:
|
|
offset++;
|
|
}
|
|
index++;
|
|
}
|
|
_lineStarts.add(offset);
|
|
}
|
|
}
|
|
|
|
void addAnnotation(int lineNo, int columnNo, String text) {
|
|
_ensureLineStarts();
|
|
int offset = _lineStarts[lineNo - 1] + (columnNo - 1);
|
|
annotations.add(new Annotation(lineNo, columnNo, offset, text));
|
|
}
|
|
|
|
String toText() {
|
|
StringBuffer sb = new StringBuffer();
|
|
List<Annotation> list = annotations.toList()
|
|
..sort((a, b) => a.offset.compareTo(b.offset));
|
|
int offset = 0;
|
|
for (Annotation annotation in list) {
|
|
sb.write(sourceCode.substring(offset, annotation.offset));
|
|
sb.write('@{${annotation.text}}');
|
|
offset = annotation.offset;
|
|
}
|
|
sb.write(sourceCode.substring(offset));
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/// Split the annotations in [annotatedCode] by [prefixes].
|
|
///
|
|
/// Returns a map containing an [AnnotatedCode] object for each prefix,
|
|
/// containing only the annotations whose text started with the given prefix.
|
|
/// If no prefix match the annotation text, the annotation is added to all
|
|
/// [AnnotatedCode] objects.
|
|
///
|
|
/// The prefixes are removed from the annotation texts in the returned
|
|
/// [AnnotatedCode] objects.
|
|
Map<String, AnnotatedCode> splitByPrefixes(
|
|
AnnotatedCode annotatedCode, Iterable<String> prefixes) {
|
|
Map<String, List<Annotation>> map = <String, List<Annotation>>{};
|
|
for (String prefix in prefixes) {
|
|
map[prefix] = <Annotation>[];
|
|
}
|
|
outer:
|
|
for (Annotation annotation in annotatedCode.annotations) {
|
|
for (String prefix in prefixes) {
|
|
if (annotation.text.startsWith(prefix)) {
|
|
map[prefix].add(new Annotation(annotation.lineNo, annotation.columnNo,
|
|
annotation.offset, annotation.text.substring(prefix.length)));
|
|
continue outer;
|
|
}
|
|
}
|
|
for (String prefix in prefixes) {
|
|
map[prefix].add(annotation);
|
|
}
|
|
}
|
|
Map<String, AnnotatedCode> split = <String, AnnotatedCode>{};
|
|
map.forEach((String prefix, List<Annotation> annotations) {
|
|
split[prefix] = new AnnotatedCode(annotatedCode.sourceCode, annotations);
|
|
});
|
|
return split;
|
|
}
|