|  | // 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. | 
|  |  | 
|  | // @dart = 2.9 | 
|  |  | 
|  | // 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 == '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() { | 
|  | Uri uri = Uri.parse('file://dummy.dart'); | 
|  | dummyLibrary = new Library(uri, fileUri: uri) | 
|  | ..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, fileUri: dummyLibrary.fileUri); | 
|  | 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); | 
|  | } |