blob: d0aecd34521940386401684d877f238841d70595 [file] [log] [blame]
// Copyright (c) 2019, 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.
import 'package:kernel/ast.dart' show Nullability;
abstract class ParsedType {
R accept<R, A>(Visitor<R, A> visitor, A a);
}
enum ParsedNullability {
// Used when the type is declared with the '?' suffix.
nullable,
// Used when the type is declared with the '*' suffix.
legacy,
// Used when the nullability suffix is omitted after the type declaration.
omitted,
}
Nullability interpretParsedNullability(ParsedNullability parsedNullability,
{Nullability ifOmitted = Nullability.nonNullable}) {
switch (parsedNullability) {
case ParsedNullability.nullable:
return Nullability.nullable;
case ParsedNullability.legacy:
return Nullability.legacy;
case ParsedNullability.omitted:
return ifOmitted;
}
}
String parsedNullabilityToString(ParsedNullability parsedNullability) {
switch (parsedNullability) {
case ParsedNullability.nullable:
return '?';
case ParsedNullability.legacy:
return '*';
case ParsedNullability.omitted:
return '';
}
}
class ParsedInterfaceType extends ParsedType {
final String name;
final List<ParsedType> arguments;
final ParsedNullability parsedNullability;
ParsedInterfaceType(this.name, this.arguments, this.parsedNullability);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write(name);
if (arguments.isNotEmpty) {
sb.write("<");
sb.writeAll(arguments, ", ");
sb.write(">");
}
sb.write(parsedNullabilityToString(parsedNullability));
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitInterfaceType(this, a);
}
}
abstract class ParsedDeclaration extends ParsedType {
final String name;
ParsedDeclaration(this.name);
}
class ParsedClass extends ParsedDeclaration {
final List<ParsedTypeVariable> typeVariables;
final ParsedInterfaceType? supertype;
final ParsedInterfaceType? mixedInType;
final List<ParsedType> interfaces;
final ParsedFunctionType? callableType;
ParsedClass(String name, this.typeVariables, this.supertype, this.mixedInType,
this.interfaces, this.callableType)
: super(name);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write("class ");
sb.write(name);
if (typeVariables.isNotEmpty) {
sb.write("<");
sb.writeAll(typeVariables, ", ");
sb.write(">");
}
if (supertype != null) {
sb.write(" extends ");
sb.write(supertype);
}
if (interfaces.isNotEmpty) {
sb.write(" implements ");
sb.writeAll(interfaces, ", ");
}
if (callableType != null) {
sb.write("{\n ");
sb.write(callableType);
sb.write("\n}");
} else {
sb.write(";");
}
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitClass(this, a);
}
}
class ParsedExtension extends ParsedDeclaration {
final List<ParsedTypeVariable> typeVariables;
final ParsedInterfaceType onType;
ParsedExtension(String name, this.typeVariables, this.onType) : super(name);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write("extension ");
sb.write(name);
if (typeVariables.isNotEmpty) {
sb.write("<");
sb.writeAll(typeVariables, ", ");
sb.write(">");
}
sb.write(" on ");
sb.write(onType);
sb.write(";");
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitExtension(this, a);
}
}
class ParsedTypedef extends ParsedDeclaration {
final List<ParsedTypeVariable> typeVariables;
final ParsedType type;
ParsedTypedef(String name, this.typeVariables, this.type) : super(name);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write("typedef ");
sb.write(name);
if (typeVariables.isNotEmpty) {
sb.write("<");
sb.writeAll(typeVariables, ", ");
sb.write(">");
}
sb.write(" ");
sb.write(type);
return "$sb;";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitTypedef(this, a);
}
}
class ParsedFunctionType extends ParsedType {
final List<ParsedTypeVariable> typeVariables;
final ParsedType returnType;
final ParsedArguments arguments;
final ParsedNullability parsedNullability;
ParsedFunctionType(this.typeVariables, this.returnType, this.arguments,
this.parsedNullability);
@override
String toString() {
StringBuffer sb = new StringBuffer();
if (typeVariables.isNotEmpty) {
sb.write("<");
sb.writeAll(typeVariables, ", ");
sb.write(">");
}
sb.write(arguments);
sb.write(" ->");
sb.write(parsedNullabilityToString(parsedNullability));
sb.write(" ");
sb.write(returnType);
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitFunctionType(this, a);
}
}
class ParsedVoidType extends ParsedType {
@override
String toString() => "void";
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitVoidType(this, a);
}
}
class ParsedTypeVariable extends ParsedType {
final String name;
final ParsedType? bound;
ParsedTypeVariable(this.name, this.bound);
@override
String toString() {
if (bound == null) return name;
StringBuffer sb = new StringBuffer();
sb.write(name);
sb.write(" extends ");
sb.write(bound);
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitTypeVariable(this, a);
}
}
class ParsedIntersectionType extends ParsedType {
final ParsedType a;
final ParsedType b;
ParsedIntersectionType(this.a, this.b);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write(a);
sb.write(" & ");
sb.write(b);
return "$sb";
}
@override
R accept<R, A>(Visitor<R, A> visitor, A a) {
return visitor.visitIntersectionType(this, a);
}
}
class ParsedArguments {
final List<ParsedType> required;
final List<ParsedType> positional;
final List<ParsedNamedArgument> named;
ParsedArguments(this.required, this.positional, this.named)
: assert(positional.isEmpty || named.isEmpty);
@override
String toString() {
StringBuffer sb = new StringBuffer();
sb.write("(");
sb.writeAll(required, ", ");
if (positional.isNotEmpty) {
if (required.isNotEmpty) {
sb.write(", ");
}
sb.write("[");
sb.writeAll(positional, ", ");
sb.write("]");
} else if (named.isNotEmpty) {
if (required.isNotEmpty) {
sb.write(", ");
}
if (named.isNotEmpty) {
sb.write("{");
sb.writeAll(named, ", ");
sb.write("}");
}
}
sb.write(")");
return "$sb";
}
}
class ParsedNamedArgument {
final bool isRequired;
final ParsedType type;
final String name;
ParsedNamedArgument(this.isRequired, this.type, this.name);
@override
String toString() {
StringBuffer sb = new StringBuffer();
if (isRequired) {
sb.write('required ');
}
sb.write(type);
sb.write(' ');
sb.write(name);
return sb.toString();
}
}
class Token {
final int charOffset;
final String? text;
final bool isIdentifier;
Token? next;
Token(this.charOffset, this.text, {this.isIdentifier: false});
bool get isEof => text == null;
}
class Parser {
Token peek;
String source;
Parser(this.peek, this.source);
bool get atEof => peek.isEof;
void advance() {
peek = peek.next!;
}
String computeLocation() {
return "${source.substring(0, peek.charOffset)}\n>>>"
"\n${source.substring(peek.charOffset)}";
}
void expect(String string) {
if (string != peek.text) {
throw "Expected '$string', "
"but got '${peek.text}'\n${computeLocation()}";
}
advance();
}
bool optional(String value) {
return value == peek.text;
}
bool optionalAdvance(String value) {
if (optional(value)) {
advance();
return true;
} else {
return false;
}
}
ParsedNullability parseNullability() {
ParsedNullability result = ParsedNullability.omitted;
if (optionalAdvance("?")) {
result = ParsedNullability.nullable;
} else if (optionalAdvance("*")) {
result = ParsedNullability.legacy;
}
return result;
}
ParsedType parseType() {
if (optional("class")) return parseClass();
if (optional("typedef")) return parseTypedef();
if (optional("extension")) return parseExtension();
List<ParsedType> results = <ParsedType>[];
do {
ParsedType type;
if (optional("(") || optional("<")) {
type = parseFunctionType();
} else if (optionalAdvance("void")) {
type = new ParsedInterfaceType(
"void", <ParsedType>[], ParsedNullability.nullable);
optionalAdvance("?");
} else if (optionalAdvance("invalid")) {
type = new ParsedInterfaceType(
"invalid", <ParsedType>[], ParsedNullability.nullable);
} else {
String name = parseName();
List<ParsedType> arguments = <ParsedType>[];
if (optional("<")) {
advance();
arguments.add(parseType());
while (optional(",")) {
advance();
arguments.add(parseType());
}
expect(">");
}
ParsedNullability parsedNullability = parseNullability();
type = new ParsedInterfaceType(name, arguments, parsedNullability);
}
results.add(type);
} while (optionalAdvance("&"));
// Parse `A & B & C` as `A & (B & C)` and not `(A & B) & C`.
ParsedType? result;
for (ParsedType type in results.reversed) {
if (result == null) {
result = type;
} else {
result = new ParsedIntersectionType(type, result);
}
}
return result!;
}
ParsedType parseReturnType() {
if (optionalAdvance("void")) return new ParsedVoidType();
return parseType();
}
ParsedFunctionType parseFunctionType() {
List<ParsedTypeVariable> typeVariables = parseTypeVariablesOpt();
ParsedArguments arguments = parseArguments();
expect("-");
expect(">");
ParsedNullability parsedNullability = parseNullability();
ParsedType returnType = parseReturnType();
return new ParsedFunctionType(
typeVariables, returnType, arguments, parsedNullability);
}
String parseName() {
if (!peek.isIdentifier) {
throw "Expected a name, "
"but got '${peek.text}'\n${computeLocation()}";
}
String result = peek.text!;
advance();
return result;
}
ParsedArguments parseArguments() {
List<ParsedType> requiredArguments = <ParsedType>[];
List<ParsedType> positionalArguments = <ParsedType>[];
List<ParsedNamedArgument> namedArguments = <ParsedNamedArgument>[];
expect("(");
do {
if (optional(")")) break;
if (optionalAdvance("[")) {
do {
positionalArguments.add(parseType());
} while (optionalAdvance(","));
expect("]");
break;
} else if (optionalAdvance("{")) {
do {
bool isRequired = optionalAdvance("required");
ParsedType type = parseType();
String name = parseName();
namedArguments.add(new ParsedNamedArgument(isRequired, type, name));
} while (optionalAdvance(","));
expect("}");
break;
} else {
requiredArguments.add(parseType());
}
} while (optionalAdvance(","));
expect(")");
return new ParsedArguments(
requiredArguments, positionalArguments, namedArguments);
}
List<ParsedTypeVariable> parseTypeVariablesOpt() {
List<ParsedTypeVariable> typeVariables = <ParsedTypeVariable>[];
if (optionalAdvance("<")) {
do {
typeVariables.add(parseTypeVariable());
} while (optionalAdvance(","));
expect(">");
}
return typeVariables;
}
ParsedTypeVariable parseTypeVariable() {
String name = parseName();
ParsedType? bound;
if (optionalAdvance("extends")) {
bound = parseType();
}
return new ParsedTypeVariable(name, bound);
}
ParsedClass parseClass() {
expect("class");
String name = parseName();
List<ParsedTypeVariable> typeVariables = parseTypeVariablesOpt();
ParsedInterfaceType? supertype;
ParsedInterfaceType? mixedInType;
if (optionalAdvance("extends")) {
supertype = parseType() as ParsedInterfaceType;
if (optionalAdvance("with")) {
mixedInType = parseType() as ParsedInterfaceType;
}
}
List<ParsedType> interfaces = <ParsedType>[];
if (optionalAdvance("implements")) {
do {
interfaces.add(parseType());
} while (optionalAdvance(","));
}
ParsedFunctionType? callableType;
if (optionalAdvance("{")) {
callableType = parseFunctionType();
expect("}");
} else {
expect(";");
}
return new ParsedClass(
name, typeVariables, supertype, mixedInType, interfaces, callableType);
}
ParsedExtension parseExtension() {
expect("extension");
String name = parseName();
List<ParsedTypeVariable> typeVariables = parseTypeVariablesOpt();
expect("on");
ParsedInterfaceType onType = parseType() as ParsedInterfaceType;
expect(";");
return new ParsedExtension(name, typeVariables, onType);
}
/// This parses a general typedef on this form:
///
/// typedef <name> <type-variables-opt> <type> ;
///
/// This is unlike Dart typedef.
ParsedTypedef parseTypedef() {
expect("typedef");
String name = parseName();
List<ParsedTypeVariable> typeVariables = parseTypeVariablesOpt();
ParsedType type = parseType();
expect(";");
return new ParsedTypedef(name, typeVariables, type);
}
}
final int codeUnitUppercaseA = 'A'.codeUnitAt(0);
final int codeUnitUppercaseZ = 'Z'.codeUnitAt(0);
bool isUppercaseLetter(int c) =>
codeUnitUppercaseA <= c && c <= codeUnitUppercaseZ;
final int codeUnitLowercaseA = 'a'.codeUnitAt(0);
final int codeUnitLowercaseZ = 'z'.codeUnitAt(0);
bool isLowercaseLetter(int c) =>
codeUnitLowercaseA <= c && c <= codeUnitLowercaseZ;
final int codeUnitUnderscore = '_'.codeUnitAt(0);
bool isUnderscore(int c) => c == codeUnitUnderscore;
final int codeUnit0 = '0'.codeUnitAt(0);
final int codeUnit9 = '9'.codeUnitAt(0);
bool isNumber(int c) => codeUnit0 <= c && c <= codeUnit9;
bool isNameStart(int c) =>
isUppercaseLetter(c) || isLowercaseLetter(c) || isUnderscore(c);
bool isNamePart(int c) => isNameStart(c) || isNumber(c);
final int codeUnitLineFeed = '\n'.codeUnitAt(0);
final int codeUnitCarriageReturn = '\r'.codeUnitAt(0);
final int codeUnitTab = '\t'.codeUnitAt(0);
final int codeUnitSpace = ' '.codeUnitAt(0);
bool isWhiteSpace(int c) =>
c == codeUnitCarriageReturn ||
c == codeUnitLineFeed ||
c == codeUnitTab ||
c == codeUnitSpace;
Token scanString(String text) {
int offset = 0;
Token? first;
Token? current;
while (offset < text.length) {
int c = text.codeUnitAt(offset);
if (isWhiteSpace(c)) {
offset++;
continue;
}
Token token;
if (isNameStart(c)) {
int startOffset = offset;
offset++;
while (offset < text.length) {
int c = text.codeUnitAt(offset);
if (isNamePart(c)) {
offset++;
} else {
break;
}
}
token = new Token(startOffset, text.substring(startOffset, offset),
isIdentifier: true);
} else {
token = new Token(offset, text.substring(offset, offset + 1));
offset += 1;
}
first ??= token;
current?.next = token;
current = token;
}
Token eof = new Token(offset, null);
if (current == null) {
current = first = eof;
} else {
current.next = eof;
}
return first!;
}
List<ParsedType> parse(String text) {
Parser parser = new Parser(scanString(text), text);
List<ParsedType> types = <ParsedType>[];
while (!parser.atEof) {
types.add(parser.parseType());
}
return types;
}
List<ParsedTypeVariable> parseTypeVariables(String text) {
Parser parser = new Parser(scanString(text), text);
List<ParsedTypeVariable> result = parser.parseTypeVariablesOpt();
if (!parser.atEof) {
throw "Expected EOF, but got '${parser.peek.text}'\n"
"${parser.computeLocation()}";
}
return result;
}
abstract class Visitor<R, A> {
R visitInterfaceType(ParsedInterfaceType node, A a);
R visitClass(ParsedClass node, A a);
R visitExtension(ParsedExtension node, A a);
R visitTypedef(ParsedTypedef node, A a);
R visitFunctionType(ParsedFunctionType node, A a);
R visitVoidType(ParsedVoidType node, A a);
R visitTypeVariable(ParsedTypeVariable node, A a);
R visitIntersectionType(ParsedIntersectionType node, A a);
}