blob: 8d38da3619d9bc733f1b233ad00ac03da8d2cc18 [file] [log] [blame]
// Copyright (c) 2017, 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.
import 'dart:typed_data';
import 'package:front_end/src/base/api_signature.dart';
import 'package:front_end/src/fasta/parser.dart'
show Listener, Parser, optional;
import 'package:front_end/src/fasta/parser/top_level_parser.dart';
import 'package:front_end/src/fasta/scanner.dart';
import 'package:front_end/src/fasta/scanner/token_constants.dart'
show STRING_TOKEN;
import 'package:front_end/src/fasta/source/directive_listener.dart';
import 'package:front_end/src/incremental/format.dart';
/// Compute the [UnlinkedUnitBuilder] for the [content].
UnlinkedUnitBuilder computeUnlinkedUnit(List<int> salt, List<int> content) {
// Scan the content.
ScannerResult scanResult = _scan(content);
Token token = scanResult.tokens;
// Parse directives.
var listener = new DirectiveListener();
new TopLevelParser(listener).parseUnit(token);
// Parse to record function bodies.
var parser = new _BodySkippingParser();
parser.parseUnit(token);
ApiSignature apiSignature = new ApiSignature();
apiSignature.addBytes(salt);
// Iterate over tokens and skip bodies.
Iterator<_BodyRange> bodyIterator = parser.bodyRanges.iterator;
bodyIterator.moveNext();
for (; token.kind != EOF_TOKEN; token = token.next) {
// Move to the body range that ends after the token.
while (bodyIterator.current != null &&
bodyIterator.current.last < token.charOffset) {
bodyIterator.moveNext();
}
// If the current body range starts before or at the token, skip it.
if (bodyIterator.current != null &&
bodyIterator.current.first <= token.charOffset) {
continue;
}
// The token is outside of a function body, add it.
if (token is! ErrorToken) {
apiSignature.addString(token.lexeme);
}
}
return new UnlinkedUnitBuilder(
apiSignature: apiSignature.toByteList(),
imports: listener.imports.map(_toUnlinkedNamespaceDirective).toList(),
exports: listener.exports.map(_toUnlinkedNamespaceDirective).toList(),
parts: listener.parts.toList(),
hasMixinApplication: parser.hasMixin);
}
/// Exclude all `native 'xyz';` token sequences.
void _excludeNativeClauses(Token token) {
while (token.kind != EOF_TOKEN) {
Token next = token.next;
if (optional('native', next) &&
next.next.kind == STRING_TOKEN &&
optional(';', next.next.next)) {
next = next.next.next;
token.setNext(next);
}
token = next;
}
}
/// Scan the content of the file.
ScannerResult _scan(List<int> content) {
var zeroTerminatedBytes = new Uint8List(content.length + 1);
zeroTerminatedBytes.setRange(0, content.length, content);
ScannerResult result = scan(zeroTerminatedBytes);
_excludeNativeClauses(result.tokens);
return result;
}
/// Convert [NamespaceCombinator] into [UnlinkedCombinatorBuilder].
UnlinkedCombinatorBuilder _toUnlinkedCombinator(NamespaceCombinator c) =>
new UnlinkedCombinatorBuilder(isShow: c.isShow, names: c.names.toList());
/// Convert [NamespaceDirective] into [UnlinkedNamespaceDirectiveBuilder].
UnlinkedNamespaceDirectiveBuilder _toUnlinkedNamespaceDirective(
NamespaceDirective directive) =>
new UnlinkedNamespaceDirectiveBuilder(
uri: directive.uri,
combinators: directive.combinators.map(_toUnlinkedCombinator).toList());
/// The char range of a function body.
class _BodyRange {
/// The char offset of the first token in the range.
final int first;
/// The char offset of the last token in the range.
final int last;
_BodyRange(this.first, this.last);
@override
String toString() => '[$first, $last]';
}
/// The [Parser] that skips function bodies and remembers their token ranges.
class _BodySkippingParser extends Parser {
bool hasMixin = false;
final List<_BodyRange> bodyRanges = [];
_BodySkippingParser() : super(new Listener());
@override
Token parseFunctionBody(
Token token, bool ofFunctionExpression, bool allowAbstract) {
Token next = token.next;
if (identical('{', next.lexeme)) {
Token close = skipBlock(token);
bodyRanges.add(new _BodyRange(next.charOffset, close.charOffset));
return close;
}
return super.parseFunctionBody(token, ofFunctionExpression, allowAbstract);
}
@override
Token parseInvalidBlock(Token token) => skipBlock(token);
Token parseMixinApplicationRest(Token token) {
hasMixin = true;
return super.parseMixinApplicationRest(token);
}
}