blob: 62b5a0fff170395054e56be2fcc9e60905939f37 [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.
// 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 QuestionMark = 14;
static const int Asterisk = 15;
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 42:
return Token.Asterisk;
case 44:
return Token.Comma;
case 60:
return Token.LeftAngle;
case 62:
return Token.RightAngle;
case 63:
return Token.QuestionMark;
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');
}
}
Nullability parseOptionalNullability(
[Nullability defaultNullability = Nullability.nonNullable]) {
int token = peekToken();
switch (token) {
case Token.QuestionMark:
scanToken();
return Nullability.nullable;
case Token.Asterisk:
scanToken();
return Nullability.legacy;
default:
return defaultNullability;
}
}
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) {
List<DartType> typeArguments = parseOptionalTypeArgumentList();
Nullability nullability = parseOptionalNullability();
return new InterfaceType(target, nullability, typeArguments);
} else if (target is Typedef) {
List<DartType> typeArguments = parseOptionalTypeArgumentList();
Nullability nullability = parseOptionalNullability();
return new TypedefType(target, nullability, typeArguments);
} else if (target is TypeParameter) {
Nullability nullability = parseOptionalNullability(null);
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,
nullability ??
TypeParameterType.computeNullabilityFromBound(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('=>');
Nullability nullability = parseOptionalNullability();
var returnType = parseType();
return new FunctionType(parameters, returnType, nullability,
namedParameters: namedParameters);
case Token.LeftAngle:
var typeParameters = parseAndPushTypeParameterList();
List<DartType> parameters = <DartType>[];
List<NamedType> namedParameters = <NamedType>[];
parseParameterList(parameters, namedParameters);
consumeString('=>');
Nullability nullability = parseOptionalNullability();
var returnType = parseType();
popTypeParameters(typeParameters);
return new FunctionType(parameters, returnType, nullability,
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'), Nullability.nullable);
}
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'))
..isNonNullableByDefault = true;
component.libraries.add(dummyLibrary..parent = component);
dummyLibrary.name = 'lib';
}
TreeNode lookup(String name) {
return name.length == 1
? typeParameters.putIfAbsent(
name,
() => new TypeParameter(name,
new InterfaceType(lookupClass('Object'), Nullability.nullable)))
: 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);
}