blob: 4962ba9012136b152a43d515d852c5faf9024a42 [file] [log] [blame]
// 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.
library sourcemap.output_structure;
import 'html_parts.dart' show
CodeLine;
// Constants used to identify the subsection of the JavaScript output. These
// are specifically for the unminified full_emitter output.
const String HEAD = ' var dart = [';
const String TAIL = ' }], ';
const String END = ' setupProgram(dart';
final RegExp TOP_LEVEL_VALUE = new RegExp(r'^ (".+?"):');
final RegExp TOP_LEVEL_FUNCTION =
new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function');
final RegExp TOP_LEVEL_CLASS = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{');
final RegExp STATICS = new RegExp(r'^ static:');
final RegExp MEMBER_VALUE = new RegExp(r'^ (".+?"):');
final RegExp MEMBER_FUNCTION =
new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function');
final RegExp MEMBER_OBJECT = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{');
final RegExp STATIC_FUNCTION =
new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function');
/// Subrange of the JavaScript output.
abstract class OutputEntity {
Interval get interval;
Interval get header;
Interval get footer;
bool get canHaveChildren => false;
List<OutputEntity> get children;
CodeSource codeSource;
Interval getChildInterval(Interval childIndex) {
return new Interval(
children[childIndex.from].interval.from,
children[childIndex.to - 1].interval.to);
}
OutputEntity getChild(int index) {
return children[index];
}
accept(OutputVisitor visitor, arg);
EntityKind get kind;
Map toJson();
OutputEntity getEntityForLine(int line);
}
enum EntityKind {
STRUCTURE,
LIBRARY,
CLASS,
TOP_LEVEL_FUNCTION,
TOP_LEVEL_VALUE,
MEMBER_FUNCTION,
MEMBER_OBJECT,
MEMBER_VALUE,
STATICS,
STATIC_FUNCTION,
}
abstract class OutputVisitor<R, A> {
R visitStructure(OutputStructure entity, A arg);
R visitLibrary(LibraryBlock entity, A arg);
R visitClass(LibraryClass entity, A arg);
R visitTopLevelFunction(TopLevelFunction entity, A arg);
R visitTopLevelValue(TopLevelValue entity, A arg);
R visitMemberObject(MemberObject entity, A arg);
R visitMemberFunction(MemberFunction entity, A arg);
R visitMemberValue(MemberValue entity, A arg);
R visitStatics(Statics entity, A arg);
R visitStaticFunction(StaticFunction entity, A arg);
}
abstract class BaseOutputVisitor<R, A> extends OutputVisitor<R, A> {
R visitEntity(OutputEntity entity, A arg) => null;
R visitStructure(OutputStructure entity, A arg) => visitEntity(entity, arg);
R visitLibrary(LibraryBlock entity, A arg) => visitEntity(entity, arg);
R visitClass(LibraryClass entity, A arg) => visitEntity(entity, arg);
R visitMember(BasicEntity entity, A arg) => visitEntity(entity, arg);
R visitTopLevelMember(BasicEntity entity, A arg) => visitMember(entity, arg);
R visitTopLevelFunction(TopLevelFunction entity, A arg) {
return visitTopLevelMember(entity, arg);
}
R visitTopLevelValue(TopLevelValue entity, A arg) {
return visitTopLevelMember(entity, arg);
}
R visitClassMember(BasicEntity entity, A arg) => visitMember(entity, arg);
R visitMemberObject(MemberObject entity, A arg) {
return visitClassMember(entity, arg);
}
R visitMemberFunction(MemberFunction entity, A arg) {
return visitClassMember(entity, arg);
}
R visitMemberValue(MemberValue entity, A arg) {
return visitClassMember(entity, arg);
}
R visitStatics(Statics entity, A arg) {
return visitClassMember(entity, arg);
}
R visitStaticFunction(StaticFunction entity, A arg) {
return visitClassMember(entity, arg);
}
}
/// The whole JavaScript output.
class OutputStructure extends OutputEntity {
final List<CodeLine> lines;
final int headerEnd;
final int footerStart;
final List<LibraryBlock> children;
OutputStructure(
this.lines,
this.headerEnd,
this.footerStart,
this.children);
@override
EntityKind get kind => EntityKind.STRUCTURE;
Interval get interval => new Interval(0, lines.length);
Interval get header => new Interval(0, headerEnd);
Interval get footer => new Interval(footerStart, lines.length);
bool get canHaveChildren => true;
OutputEntity getEntityForLine(int line) {
if (line < headerEnd || line >= footerStart) {
return this;
}
for (LibraryBlock library in children) {
if (library.interval.contains(line)) {
return library.getEntityForLine(line);
}
}
return null;
}
/// Compute the structure of the JavaScript [lines].
static OutputStructure parse(List<CodeLine> lines) {
int findHeaderStart(List<CodeLine> lines) {
int index = 0;
for (CodeLine line in lines) {
if (line.code.startsWith(HEAD)) {
return index;
}
index++;
}
return lines.length;
}
int findHeaderEnd(int start, List<CodeLine> lines) {
int index = start;
for (CodeLine line in lines.skip(start)) {
if (line.code.startsWith(END)) {
return index;
}
index++;
}
return lines.length;
}
String readHeader(CodeLine line) {
String code = line.code;
String ssaLineHeader;
if (code.startsWith(HEAD)) {
return code.substring(HEAD.length);
} else if (code.startsWith(TAIL)) {
return code.substring(TAIL.length);
}
return null;
}
List<LibraryBlock> computeHeaderMap(
List<CodeLine> lines, int start, int end) {
List<LibraryBlock> libraryBlocks = <LibraryBlock>[];
LibraryBlock current;
for (int index = start; index < end; index++) {
String header = readHeader(lines[index]);
if (header != null) {
if (current != null) {
current.to = index;
}
libraryBlocks.add(current = new LibraryBlock(header, index));
}
}
if (current != null) {
current.to = end;
}
return libraryBlocks;
}
int headerEnd = findHeaderStart(lines);
int footerStart = findHeaderEnd(headerEnd, lines);
List<LibraryBlock> libraryBlocks =
computeHeaderMap(lines, headerEnd, footerStart);
for (LibraryBlock block in libraryBlocks) {
block.preprocess(lines);
}
return new OutputStructure(
lines, headerEnd, footerStart, libraryBlocks);
}
accept(OutputVisitor visitor, arg) => visitor.visitStructure(this, arg);
@override
Map toJson() {
return {
'lines': lines.map((line) => line.toJson()).toList(),
'headerEnd': headerEnd,
'footerStart': footerStart,
'children': children.map((child) => child.toJson()).toList(),
};
}
static OutputStructure fromJson(Map json) {
List<CodeLine> lines = json['lines'].map(CodeLine.fromJson).toList();
int headerEnd = json['headerEnd'];
int footerStart = json['footerStart'];
List<LibraryBlock> children =
json['children'].map(AbstractEntity.fromJson).toList();
return new OutputStructure(lines, headerEnd, footerStart, children);
}
}
abstract class AbstractEntity extends OutputEntity {
final String name;
final int from;
int to;
AbstractEntity(this.name, this.from);
Interval get interval => new Interval(from, to);
@override
Map toJson() {
return {
'kind': kind.index,
'name': name,
'from': from,
'to': to,
'children': children.map((child) => child.toJson()).toList(),
'codeSource': codeSource != null ? codeSource.toJson() : null,
};
}
static AbstractEntity fromJson(Map json) {
EntityKind kind = EntityKind.values[json['kind']];
String name = json['name'];
int from = json['from'];
int to = json['to'];
CodeSource codeSource = CodeSource.fromJson(json['codeSource']);
switch (kind) {
case EntityKind.STRUCTURE:
throw new StateError('Unexpected entity kind $kind');
case EntityKind.LIBRARY:
LibraryBlock lib = new LibraryBlock(name, from)
..to = to
..codeSource = codeSource;
json['children'].forEach((child) => lib.children.add(fromJson(child)));
return lib;
case EntityKind.CLASS:
LibraryClass cls = new LibraryClass(name, from)
..to = to
..codeSource = codeSource;
json['children'].forEach((child) => cls.children.add(fromJson(child)));
return cls;
case EntityKind.TOP_LEVEL_FUNCTION:
return new TopLevelFunction(name, from)
..to = to
..codeSource = codeSource;
case EntityKind.TOP_LEVEL_VALUE:
return new TopLevelValue(name, from)
..to = to
..codeSource = codeSource;
case EntityKind.MEMBER_FUNCTION:
return new MemberFunction(name, from)
..to = to
..codeSource = codeSource;
case EntityKind.MEMBER_OBJECT:
return new MemberObject(name, from)
..to = to
..codeSource = codeSource;
case EntityKind.MEMBER_VALUE:
return new MemberValue(name, from)
..to = to
..codeSource = codeSource;
case EntityKind.STATICS:
Statics statics = new Statics(from)
..to = to
..codeSource = codeSource;
json['children'].forEach(
(child) => statics.children.add(fromJson(child)));
return statics;
case EntityKind.STATIC_FUNCTION:
return new StaticFunction(name, from)
..to = to
..codeSource = codeSource;
}
}
}
/// A block defining the content of a Dart library.
class LibraryBlock extends AbstractEntity {
List<BasicEntity> children = <BasicEntity>[];
int get headerEnd => from + 2;
int get footerStart => to - 1;
LibraryBlock(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.LIBRARY;
Interval get header => new Interval(from, headerEnd);
Interval get footer => new Interval(footerStart, to);
bool get canHaveChildren => true;
void preprocess(List<CodeLine> lines) {
int index = headerEnd;
BasicEntity current;
while (index < footerStart) {
String line = lines[index].code;
BasicEntity next;
Match matchFunction = TOP_LEVEL_FUNCTION.firstMatch(line);
if (matchFunction != null) {
next = new TopLevelFunction(matchFunction.group(1), index);
} else {
Match matchClass = TOP_LEVEL_CLASS.firstMatch(line);
if (matchClass != null) {
next = new LibraryClass(matchClass.group(1), index);
} else {
Match matchValue = TOP_LEVEL_VALUE.firstMatch(line);
if (matchValue != null) {
next = new TopLevelValue(matchValue.group(1), index);
}
}
}
if (next != null) {
if (current != null) {
current.to = index;
}
children.add(current = next);
} else if (index == headerEnd) {
throw 'Failed to match first library block line:\n$line';
}
index++;
}
if (current != null) {
current.to = footerStart;
}
for (BasicEntity entity in children) {
entity.preprocess(lines);
}
}
accept(OutputVisitor visitor, arg) => visitor.visitLibrary(this, arg);
OutputEntity getEntityForLine(int line) {
if (line < headerEnd || line >= footerStart) {
return this;
}
for (BasicEntity child in children) {
if (child.interval.contains(line)) {
return child.getEntityForLine(line);
}
}
return null;
}
}
/// A simple member of a library or class.
abstract class BasicEntity extends AbstractEntity {
BasicEntity(String name, int from) : super(name, from);
Interval get header => new Interval(from, to);
Interval get footer => new Interval(to, to);
List<OutputEntity> get children => const <OutputEntity>[];
void preprocess(List<CodeLine> lines) {}
@override
OutputEntity getEntityForLine(int line) {
if (interval.contains(line)) {
return this;
}
return null;
}
}
class TopLevelFunction extends BasicEntity {
TopLevelFunction(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.TOP_LEVEL_FUNCTION;
accept(OutputVisitor visitor, arg) {
return visitor.visitTopLevelFunction(this, arg);
}
}
class TopLevelValue extends BasicEntity {
TopLevelValue(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.TOP_LEVEL_VALUE;
accept(OutputVisitor visitor, arg) {
return visitor.visitTopLevelValue(this, arg);
}
}
/// A block defining a Dart class.
class LibraryClass extends BasicEntity {
List<BasicEntity> children = <BasicEntity>[];
int get headerEnd => from + 1;
int get footerStart => to - 1;
LibraryClass(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.CLASS;
Interval get header => new Interval(from, headerEnd);
Interval get footer => new Interval(footerStart, to);
bool get canHaveChildren => true;
void preprocess(List<CodeLine> lines) {
int index = headerEnd;
BasicEntity current;
while (index < footerStart) {
String line = lines[index].code;
BasicEntity next;
Match match = MEMBER_FUNCTION.firstMatch(line);
if (match != null) {
next = new MemberFunction(match.group(1), index);
} else {
match = STATICS.firstMatch(line);
if (match != null) {
next = new Statics(index);
} else {
match = MEMBER_OBJECT.firstMatch(line);
if (match != null) {
next = new MemberObject(match.group(1), index);
} else {
match = MEMBER_VALUE.firstMatch(line);
if (match != null) {
next = new MemberValue(match.group(1), index);
}
}
}
}
if (next != null) {
if (current != null) {
current.to = index;
}
children.add(current = next);
} else if (index == headerEnd) {
throw 'Failed to match first library block line:\n$line';
}
index++;
}
if (current != null) {
current.to = footerStart;
}
for (BasicEntity entity in children) {
entity.preprocess(lines);
}
}
accept(OutputVisitor visitor, arg) => visitor.visitClass(this, arg);
OutputEntity getEntityForLine(int line) {
if (line < headerEnd || line >= footerStart) {
return this;
}
for (BasicEntity child in children) {
if (child.interval.contains(line)) {
return child.getEntityForLine(line);
}
}
return null;
}
}
/// A block defining static members of a Dart class.
class Statics extends BasicEntity {
List<BasicEntity> children = <BasicEntity>[];
int get headerEnd => from + 1;
int get footerStart => to - 1;
Statics(int from) : super('statics', from);
@override
EntityKind get kind => EntityKind.STATICS;
Interval get header => new Interval(from, headerEnd);
Interval get footer => new Interval(footerStart, to);
bool get canHaveChildren => true;
void preprocess(List<CodeLine> lines) {
int index = headerEnd;
BasicEntity current;
while (index < footerStart) {
String line = lines[index].code;
BasicEntity next;
Match matchFunction = STATIC_FUNCTION.firstMatch(line);
if (matchFunction != null) {
next = new MemberFunction(matchFunction.group(1), index);
}
if (next != null) {
if (current != null) {
current.to = index;
}
children.add(current = next);
} else if (index == headerEnd) {
throw 'Failed to match first statics line:\n$line';
}
index++;
}
if (current != null) {
current.to = footerStart;
}
}
accept(OutputVisitor visitor, arg) => visitor.visitStatics(this, arg);
OutputEntity getEntityForLine(int line) {
if (line < headerEnd || line >= footerStart) {
return this;
}
for (BasicEntity child in children) {
if (child.interval.contains(line)) {
return child.getEntityForLine(line);
}
}
return null;
}
}
class MemberFunction extends BasicEntity {
MemberFunction(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.MEMBER_FUNCTION;
accept(OutputVisitor visitor, arg) => visitor.visitMemberFunction(this, arg);
}
class MemberObject extends BasicEntity {
MemberObject(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.MEMBER_OBJECT;
accept(OutputVisitor visitor, arg) => visitor.visitMemberObject(this, arg);
}
class MemberValue extends BasicEntity {
MemberValue(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.MEMBER_VALUE;
accept(OutputVisitor visitor, arg) => visitor.visitMemberValue(this, arg);
}
class StaticFunction extends BasicEntity {
StaticFunction(String name, int from) : super(name, from);
@override
EntityKind get kind => EntityKind.STATIC_FUNCTION;
accept(OutputVisitor visitor, arg) => visitor.visitStaticFunction(this, arg);
}
class Interval {
final int from;
final int to;
const Interval(this.from, this.to);
int get length => to - from;
bool contains(int value) {
return from <= value && value < to;
}
}
enum CodeKind {
LIBRARY,
CLASS,
MEMBER,
}
class CodeSource {
final CodeKind kind;
final Uri uri;
final String name;
final int begin;
final int end;
CodeSource(this.kind, this.uri, this.name, this.begin, this.end);
String toString() => '${toJson()}';
Map toJson() {
return {
'kind': kind.index,
'uri': uri.toString(),
'name': name,
'begin': begin,
'end': end,
};
}
static CodeSource fromJson(Map json) {
if (json == null) return null;
return new CodeSource(
CodeKind.values[json['kind']],
Uri.parse(json['uri']),
json['name'],
json['begin'],
json['end']);
}
}