| // 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); |
| } |