mirror of
https://github.com/dart-lang/sdk
synced 2024-09-16 04:16:51 +00:00
4f2bdff90b
Change-Id: I1a3cc03fba9783807fa637a9d42fdbad68ee7686 Reviewed-on: https://dart-review.googlesource.com/31040 Commit-Queue: Peter von der Ahé <ahe@google.com> Reviewed-by: Kevin Millikin <kmillikin@google.com>
356 lines
10 KiB
Dart
356 lines
10 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.
|
|
// A very simple parser for a subset of DartTypes for use in testing type
|
|
// algebra.
|
|
library kernel.test.type_parser;
|
|
|
|
import 'package:kernel/kernel.dart';
|
|
import 'package:kernel/text/ast_to_text.dart';
|
|
|
|
typedef TreeNode TypeEnvironment(String name);
|
|
|
|
/// [lookupType] should return a [Class] or [TypeParameter].
|
|
DartType parseDartType(String type, TreeNode lookupType(String name)) {
|
|
return new DartTypeParser(type, lookupType).parseType();
|
|
}
|
|
|
|
class Token {
|
|
static const int Eof = 0;
|
|
static const int Name = 1;
|
|
static const int Comma = 2;
|
|
static const int LeftAngle = 3; // '<'
|
|
static const int RightAngle = 4; // '>'
|
|
static const int LeftParen = 5;
|
|
static const int RightParen = 6;
|
|
static const int LeftBracket = 7;
|
|
static const int RightBracket = 8;
|
|
static const int LeftBrace = 9;
|
|
static const int RightBrace = 10;
|
|
static const int Arrow = 11; // '=>'
|
|
static const int Colon = 12;
|
|
static const int Ampersand = 13;
|
|
static const int Invalid = 100;
|
|
}
|
|
|
|
class DartTypeParser {
|
|
final String string;
|
|
int index = 0;
|
|
String tokenText;
|
|
final TypeEnvironment environment;
|
|
final Map<String, TypeParameter> localTypeParameters =
|
|
<String, TypeParameter>{};
|
|
|
|
DartTypeParser(this.string, this.environment);
|
|
|
|
TreeNode lookupType(String name) {
|
|
return localTypeParameters[name] ?? environment(name);
|
|
}
|
|
|
|
bool isIdentifierChar(int charCode) {
|
|
return 65 <= charCode && charCode <= 90 ||
|
|
97 <= charCode && charCode <= 122 ||
|
|
charCode == 95 || // '_'
|
|
charCode == 36; // '$'
|
|
}
|
|
|
|
bool isWhitespaceChar(int charCode) {
|
|
return charCode == 32;
|
|
}
|
|
|
|
int next() => string.codeUnitAt(index++);
|
|
int peek() => index < string.length ? string.codeUnitAt(index) : 0;
|
|
|
|
void skipWhitespace() {
|
|
while (isWhitespaceChar(peek())) {
|
|
next();
|
|
}
|
|
}
|
|
|
|
int scanToken() {
|
|
skipWhitespace();
|
|
if (index >= string.length) return Token.Eof;
|
|
int startIndex = index;
|
|
int x = next();
|
|
if (isIdentifierChar(x)) {
|
|
while (isIdentifierChar(peek())) {
|
|
x = next();
|
|
}
|
|
tokenText = string.substring(startIndex, index);
|
|
return Token.Name;
|
|
} else {
|
|
tokenText = string[index - 1];
|
|
int type = getTokenType(x);
|
|
return type;
|
|
}
|
|
}
|
|
|
|
int peekToken() {
|
|
skipWhitespace();
|
|
if (index >= string.length) return Token.Eof;
|
|
return getTokenType(peek());
|
|
}
|
|
|
|
int getTokenType(int character) {
|
|
switch (character) {
|
|
case 38:
|
|
return Token.Ampersand;
|
|
case 44:
|
|
return Token.Comma;
|
|
case 60:
|
|
return Token.LeftAngle;
|
|
case 62:
|
|
return Token.RightAngle;
|
|
case 40:
|
|
return Token.LeftParen;
|
|
case 41:
|
|
return Token.RightParen;
|
|
case 91:
|
|
return Token.LeftBracket;
|
|
case 92:
|
|
return Token.RightBracket;
|
|
case 123:
|
|
return Token.LeftBrace;
|
|
case 125:
|
|
return Token.RightBrace;
|
|
case 58:
|
|
return Token.Colon;
|
|
default:
|
|
if (isIdentifierChar(character)) return Token.Name;
|
|
return Token.Invalid;
|
|
}
|
|
}
|
|
|
|
void consumeString(String text) {
|
|
skipWhitespace();
|
|
if (string.startsWith(text, index)) {
|
|
index += text.length;
|
|
} else {
|
|
return fail('Expected token $text');
|
|
}
|
|
}
|
|
|
|
DartType parseType() {
|
|
int token = peekToken();
|
|
switch (token) {
|
|
case Token.Name:
|
|
scanToken();
|
|
String name = this.tokenText;
|
|
if (name == '_') return const BottomType();
|
|
if (name == 'void') return const VoidType();
|
|
if (name == 'dynamic') return const DynamicType();
|
|
var target = lookupType(name);
|
|
if (target == null) {
|
|
return fail('Unresolved type $name');
|
|
} else if (target is Class) {
|
|
return new InterfaceType(target, parseOptionalTypeArgumentList());
|
|
} else if (target is Typedef) {
|
|
return new TypedefType(target, parseOptionalTypeArgumentList());
|
|
} else if (target is TypeParameter) {
|
|
DartType promotedBound;
|
|
switch (peekToken()) {
|
|
case Token.LeftAngle:
|
|
return fail('Attempt to apply type arguments to a type variable');
|
|
case Token.Ampersand:
|
|
scanToken();
|
|
promotedBound = parseType();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return new TypeParameterType(target, promotedBound);
|
|
}
|
|
return fail("Unexpected lookup result for $name: $target");
|
|
|
|
case Token.LeftParen:
|
|
List<DartType> parameters = <DartType>[];
|
|
List<NamedType> namedParameters = <NamedType>[];
|
|
parseParameterList(parameters, namedParameters);
|
|
consumeString('=>');
|
|
var returnType = parseType();
|
|
return new FunctionType(parameters, returnType,
|
|
namedParameters: namedParameters);
|
|
|
|
case Token.LeftAngle:
|
|
var typeParameters = parseAndPushTypeParameterList();
|
|
List<DartType> parameters = <DartType>[];
|
|
List<NamedType> namedParameters = <NamedType>[];
|
|
parseParameterList(parameters, namedParameters);
|
|
consumeString('=>');
|
|
var returnType = parseType();
|
|
popTypeParameters(typeParameters);
|
|
return new FunctionType(parameters, returnType,
|
|
typeParameters: typeParameters, namedParameters: namedParameters);
|
|
|
|
default:
|
|
return fail('Unexpected token: $tokenText');
|
|
}
|
|
}
|
|
|
|
void parseParameterList(List<DartType> positional, List<NamedType> named) {
|
|
int token = scanToken();
|
|
assert(token == Token.LeftParen);
|
|
token = peekToken();
|
|
while (token != Token.RightParen) {
|
|
var type = parseType(); // Could be a named parameter name.
|
|
token = scanToken();
|
|
if (token == Token.Colon) {
|
|
String name = convertTypeToParameterName(type);
|
|
named.add(new NamedType(name, parseType()));
|
|
token = scanToken();
|
|
} else {
|
|
positional.add(type);
|
|
}
|
|
if (token != Token.Comma && token != Token.RightParen) {
|
|
return fail('Unterminated parameter list');
|
|
}
|
|
}
|
|
named.sort();
|
|
}
|
|
|
|
String convertTypeToParameterName(DartType type) {
|
|
if (type is InterfaceType && type.typeArguments.isEmpty) {
|
|
return type.classNode.name;
|
|
} else if (type is TypeParameterType) {
|
|
return type.parameter.name;
|
|
} else {
|
|
return fail('Unexpected colon after $type');
|
|
}
|
|
}
|
|
|
|
List<DartType> parseTypeList(int open, int close) {
|
|
int token = scanToken();
|
|
assert(token == open);
|
|
List<DartType> types = <DartType>[];
|
|
token = peekToken();
|
|
while (token != close) {
|
|
types.add(parseType());
|
|
token = scanToken();
|
|
if (token != Token.Comma && token != close) {
|
|
throw fail('Unterminated list');
|
|
}
|
|
}
|
|
return types;
|
|
}
|
|
|
|
List<DartType> parseOptionalList(int open, int close) {
|
|
if (peekToken() != open) return null;
|
|
return parseTypeList(open, close);
|
|
}
|
|
|
|
List<DartType> parseOptionalTypeArgumentList() {
|
|
return parseOptionalList(Token.LeftAngle, Token.RightAngle);
|
|
}
|
|
|
|
void popTypeParameters(List<TypeParameter> typeParameters) {
|
|
typeParameters.forEach(localTypeParameters.remove);
|
|
}
|
|
|
|
List<TypeParameter> parseAndPushTypeParameterList() {
|
|
int token = scanToken();
|
|
assert(token == Token.LeftAngle);
|
|
List<TypeParameter> typeParameters = <TypeParameter>[];
|
|
token = peekToken();
|
|
while (token != Token.RightAngle) {
|
|
typeParameters.add(parseAndPushTypeParameter());
|
|
token = scanToken();
|
|
if (token != Token.Comma && token != Token.RightAngle) {
|
|
throw fail('Unterminated type parameter list');
|
|
}
|
|
}
|
|
return typeParameters;
|
|
}
|
|
|
|
TypeParameter parseAndPushTypeParameter() {
|
|
var nameTok = scanToken();
|
|
if (nameTok != Token.Name) return fail('Expected a name');
|
|
var typeParameter = new TypeParameter(tokenText);
|
|
if (localTypeParameters.containsKey(typeParameter.name)) {
|
|
return fail('Shadowing a type parameter is not allowed');
|
|
}
|
|
localTypeParameters[typeParameter.name] = typeParameter;
|
|
var next = peekToken();
|
|
if (next == Token.Colon) {
|
|
scanToken();
|
|
typeParameter.bound = parseType();
|
|
} else {
|
|
typeParameter.bound = new InterfaceType(lookupType('Object'));
|
|
}
|
|
return typeParameter;
|
|
}
|
|
|
|
dynamic fail(String message) {
|
|
throw '$message at index $index';
|
|
}
|
|
}
|
|
|
|
class LazyTypeEnvironment {
|
|
final Map<String, Class> classes = <String, Class>{};
|
|
final Map<String, TypeParameter> typeParameters = <String, TypeParameter>{};
|
|
Library dummyLibrary;
|
|
final Component component = new Component();
|
|
|
|
LazyTypeEnvironment() {
|
|
dummyLibrary = new Library(Uri.parse('file://dummy.dart'));
|
|
component.libraries.add(dummyLibrary..parent = component);
|
|
dummyLibrary.name = 'lib';
|
|
}
|
|
|
|
TreeNode lookup(String name) {
|
|
return name.length == 1
|
|
? typeParameters.putIfAbsent(
|
|
name, () => new TypeParameter(name, lookupClass('Object').rawType))
|
|
: lookupClass(name);
|
|
}
|
|
|
|
Class lookupClass(String name) {
|
|
return classes.putIfAbsent(name, () => makeClass(name));
|
|
}
|
|
|
|
Class makeClass(String name) {
|
|
var class_ = new Class(name: name);
|
|
dummyLibrary.addClass(class_);
|
|
return class_;
|
|
}
|
|
|
|
void clearTypeParameters() {
|
|
typeParameters.clear();
|
|
}
|
|
|
|
void setupTypeParameters(String typeParametersList) {
|
|
for (var typeParameter
|
|
in new DartTypeParser('<$typeParametersList>', lookup)
|
|
.parseAndPushTypeParameterList()) {
|
|
typeParameters[typeParameter.name] = typeParameter;
|
|
}
|
|
}
|
|
|
|
DartType parse(String type) => parseDartType(type, lookup);
|
|
|
|
Supertype parseSuper(String type) {
|
|
InterfaceType interfaceType = parse(type);
|
|
return new Supertype(interfaceType.classNode, interfaceType.typeArguments);
|
|
}
|
|
|
|
DartType parseFresh(String type) {
|
|
clearTypeParameters();
|
|
return parse(type);
|
|
}
|
|
|
|
TypeParameter getTypeParameter(String name) {
|
|
if (name.length != 1) throw 'Type parameter names must have length 1';
|
|
return lookup(name);
|
|
}
|
|
}
|
|
|
|
void main(List<String> args) {
|
|
if (args.length != 1) {
|
|
print('Usage: type_parser TYPE');
|
|
}
|
|
var environment = new LazyTypeEnvironment();
|
|
var type = parseDartType(args[0], environment.lookup);
|
|
var buffer = new StringBuffer();
|
|
new Printer(buffer).writeType(type);
|
|
print(buffer);
|
|
}
|