blob: 4e3ae62a3a8c0beb2a0114175ff97af93d5c85b4 [file] [log] [blame]
// Copyright (c) 2017, 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 'dart:async';
import 'dart:io';
import 'dart:isolate';
import 'package:yaml/yaml.dart' show loadYaml;
import 'package:dart_style/dart_style.dart' show DartFormatter;
import "package:front_end/src/fasta/severity.dart" show severityEnumNames;
main(List<String> arguments) async {
var port = new ReceivePort();
await new File.fromUri(await computeGeneratedFile())
.writeAsString(await generateMessagesFile(), flush: true);
port.close();
}
Future<Uri> computeGeneratedFile() {
return Isolate.resolvePackageUri(
Uri.parse('package:front_end/src/fasta/fasta_codes_generated.dart'));
}
Future<String> generateMessagesFile() async {
Uri messagesFile = Platform.script.resolve("../../messages.yaml");
Map<dynamic, dynamic> yaml =
loadYaml(await new File.fromUri(messagesFile).readAsStringSync());
StringBuffer sb = new StringBuffer();
sb.writeln("""
// Copyright (c) 2018, 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.
// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
//
// Instead modify 'pkg/front_end/messages.yaml' and run
// 'pkg/front_end/tool/fasta generate-messages' to update.
part of fasta.codes;
""");
bool hasError = false;
int largestIndex = 0;
final indexNameMap = new Map<int, String>();
List<String> keys = yaml.keys.cast<String>().toList()..sort();
for (String name in keys) {
var description = yaml[name];
while (description is String) {
description = yaml[description];
}
Map<dynamic, dynamic> map = description;
if (map == null) {
throw "No 'template:' in key $name.";
}
var index = map['index'];
if (index != null) {
if (index is! int || index < 1) {
print('Error: Expected positive int for "index:" field in $name,'
' but found $index');
hasError = true;
index = -1;
// Continue looking for other problems.
} else {
String otherName = indexNameMap[index];
if (otherName != null) {
print('Error: The "index:" field must be unique, '
'but is the same for $otherName and $name');
hasError = true;
// Continue looking for other problems.
} else {
indexNameMap[index] = name;
if (largestIndex < index) {
largestIndex = index;
}
}
}
}
sb.writeln(compileTemplate(name, index, map['template'], map['tip'],
map['analyzerCode'], map['severity']));
}
if (largestIndex > indexNameMap.length) {
print('Error: The "index:" field values should be unique, consecutive'
' whole numbers starting with 1.');
hasError = true;
// Fall through to print more information.
}
if (hasError) {
exitCode = 1;
print('The largest index is $largestIndex');
final sortedIndices = indexNameMap.keys.toList()..sort();
int nextAvailableIndex = largestIndex + 1;
for (int index = 1; index <= sortedIndices.length; ++index) {
if (sortedIndices[index - 1] != index) {
nextAvailableIndex = index;
break;
}
}
print('The next available index is ${nextAvailableIndex}');
return '';
}
return new DartFormatter().format("$sb");
}
final RegExp placeholderPattern =
new RegExp("#\([-a-zA-Z0-9_]+\)(?:%\([0-9]*\)\.\([0-9]+\))?");
String compileTemplate(String name, int index, String template, String tip,
Object analyzerCode, String severity) {
if (template == null) {
print('Error: missing template for message: $name');
exitCode = 1;
return '';
}
// Remove trailing whitespace. This is necessary for templates defined with
// `|` (verbatim) as they always contain a trailing newline that we don't
// want.
template = template.trimRight();
var parameters = new Set<String>();
var conversions = new Set<String>();
var conversions2 = new Set<String>();
var arguments = new Set<String>();
bool hasLabeler = false;
void ensureLabeler() {
if (hasLabeler) return;
conversions.add("DummyTypeLabeler labeler = new DummyTypeLabeler();");
hasLabeler = true;
}
for (Match match in placeholderPattern.allMatches("$template${tip ?? ''}")) {
String name = match[1];
String padding = match[2];
String fractionDigits = match[3];
String format(String name) {
String conversion;
if (fractionDigits == null) {
conversion = "'\$$name'";
} else {
conversion = "$name.toStringAsFixed($fractionDigits)";
}
if (padding.isNotEmpty) {
if (padding.startsWith("0")) {
conversion += ".padLeft(${int.parse(padding)}, '0')";
} else {
conversion += ".padLeft(${int.parse(padding)})";
}
}
return conversion;
}
switch (name) {
case "character":
parameters.add("String character");
conversions.add("if (character.runes.length != 1)"
"throw \"Not a character '\${character}'\";");
arguments.add("'character': character");
break;
case "unicode":
// Write unicode value using at least four (but otherwise no more than
// necessary) hex digits, using uppercase letters.
// http://www.unicode.org/versions/Unicode10.0.0/appA.pdf
parameters.add("int codePoint");
conversions.add("String unicode = \"U+\${codePoint.toRadixString(16)"
".toUpperCase().padLeft(4, '0')}\";");
arguments.add("'codePoint': codePoint");
break;
case "name":
parameters.add("String name");
conversions.add("if (name.isEmpty) throw 'No name provided';");
arguments.add("'name': name");
conversions.add("name = demangleMixinApplicationName(name);");
break;
case "name2":
parameters.add("String name2");
conversions.add("if (name2.isEmpty) throw 'No name provided';");
arguments.add("'name2': name2");
conversions.add("name2 = demangleMixinApplicationName(name2);");
break;
case "name3":
parameters.add("String name3");
conversions.add("if (name3.isEmpty) throw 'No name provided';");
arguments.add("'name3': name3");
conversions.add("name3 = demangleMixinApplicationName(name3);");
break;
case "lexeme":
parameters.add("Token token");
conversions.add("String lexeme = token.lexeme;");
arguments.add("'token': token");
break;
case "lexeme2":
parameters.add("Token token2");
conversions.add("String lexeme2 = token2.lexeme;");
arguments.add("'token2': token2");
break;
case "string":
parameters.add("String string");
conversions.add("if (string.isEmpty) throw 'No string provided';");
arguments.add("'string': string");
break;
case "string2":
parameters.add("String string2");
conversions.add("if (string2.isEmpty) throw 'No string provided';");
arguments.add("'string2': string2");
break;
case "string3":
parameters.add("String string3");
conversions.add("if (string3.isEmpty) throw 'No string provided';");
arguments.add("'string3': string3");
break;
case "type":
case "type2":
case "type3":
parameters.add("DartType _${name}");
ensureLabeler();
conversions
.add("List<Object> ${name}Parts = labeler.labelType(_${name});");
conversions2.add("String ${name} = ${name}Parts.join();");
arguments.add("'${name}': _${name}");
break;
case "uri":
parameters.add("Uri uri_");
conversions.add("String uri = relativizeUri(uri_);");
arguments.add("'uri': uri_");
break;
case "uri2":
parameters.add("Uri uri2_");
conversions.add("String uri2 = relativizeUri(uri2_);");
arguments.add("'uri2': uri2_");
break;
case "uri3":
parameters.add("Uri uri3_");
conversions.add("String uri3 = relativizeUri(uri3_);");
arguments.add("'uri3': uri3_");
break;
case "count":
parameters.add("int count");
conversions.add("if (count == null) throw 'No count provided';");
arguments.add("'count': count");
break;
case "count2":
parameters.add("int count2");
conversions.add("if (count2 == null) throw 'No count provided';");
arguments.add("'count2': count2");
break;
case "constant":
parameters.add("Constant _constant");
ensureLabeler();
conversions.add(
"List<Object> ${name}Parts = labeler.labelConstant(_${name});");
conversions2.add("String ${name} = ${name}Parts.join();");
arguments.add("'constant': _constant");
break;
case "num1":
parameters.add("num _num1");
conversions.add("if (_num1 == null) throw 'No number provided';");
conversions.add("String num1 = ${format('_num1')};");
arguments.add("'num1': _num1");
break;
case "num2":
parameters.add("num _num2");
conversions.add("if (_num2 == null) throw 'No number provided';");
conversions.add("String num2 = ${format('_num2')};");
arguments.add("'num2': _num2");
break;
case "num3":
parameters.add("num _num3");
conversions.add("if (_num3 == null) throw 'No number provided';");
conversions.add("String num3 = ${format('_num3')};");
arguments.add("'num3': _num3");
break;
default:
throw "Unhandled placeholder in template: '$name'";
}
}
conversions.addAll(conversions2);
String interpolate(String text) {
text = text
.replaceAll(r"$", r"\$")
.replaceAllMapped(placeholderPattern, (Match m) => "\${${m[1]}}");
return "\"\"\"$text\"\"\"";
}
List<String> codeArguments = <String>[];
if (index != null) {
codeArguments.add('index: $index');
} else if (analyzerCode != null) {
if (analyzerCode is String) {
analyzerCode = <String>[analyzerCode];
}
List<Object> codes = analyzerCode;
// If "index:" is defined, then "analyzerCode:" should not be generated
// in the front end. See comment in messages.yaml
codeArguments.add('analyzerCodes: <String>["${codes.join('", "')}"]');
}
if (severity != null) {
String severityEnumName = severityEnumNames[severity];
if (severityEnumName == null) {
throw "Unknown severity '$severity'";
}
codeArguments.add('severity: Severity.$severityEnumName');
}
if (parameters.isEmpty && conversions.isEmpty && arguments.isEmpty) {
if (template != null) {
codeArguments.add('message: r"""$template"""');
}
if (tip != null) {
codeArguments.add('tip: r"""$tip"""');
}
return """
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> code$name = message$name;
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const MessageCode message$name =
const MessageCode(\"$name\", ${codeArguments.join(', ')});
""";
}
List<String> templateArguments = <String>[];
if (template != null) {
templateArguments.add('messageTemplate: r"""$template"""');
}
if (tip != null) {
templateArguments.add('tipTemplate: r"""$tip"""');
}
templateArguments.add("withArguments: _withArguments$name");
List<String> messageArguments = <String>[];
String message = interpolate(template);
if (hasLabeler) {
message += " + labeler.originMessages";
}
messageArguments.add("message: ${message}");
if (tip != null) {
messageArguments.add("tip: ${interpolate(tip)}");
}
messageArguments.add("arguments: { ${arguments.join(', ')} }");
return """
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(${parameters.join(', ')})> template$name =
const Template<Message Function(${parameters.join(', ')})>(
${templateArguments.join(', ')});
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(${parameters.join(', ')})> code$name =
const Code<Message Function(${parameters.join(', ')})>(
\"$name\", template$name, ${codeArguments.join(', ')});
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArguments$name(${parameters.join(', ')}) {
${conversions.join('\n ')}
return new Message(
code$name,
${messageArguments.join(', ')});
}
""";
}