| /** |
| * This file contains code to generate scanner and parser message |
| * based on the information in pkg/front_end/messages.yaml. |
| * |
| * For each message in messages.yaml that contains an 'index:' field, |
| * this tool generates an error with the name specified by the 'analyzerCode:' |
| * field and an entry in the fastaAnalyzerErrorList for that generated error. |
| * The text in the 'analyzerCode:' field must contain the name of the class |
| * containing the error and the name of the error separated by a `.` |
| * (e.g. ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND). |
| * |
| * It is expected that 'pkg/front_end/tool/fasta generate-messages' |
| * has already been successfully run. |
| */ |
| import 'dart:io'; |
| |
| import 'package:analyzer/error/error.dart'; |
| import 'package:analysis_tool/tools.dart'; |
| import 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| import 'package:front_end/src/testing/package_root.dart' as pkgRoot; |
| import 'package:front_end/src/fasta/scanner.dart'; |
| import 'package:path/path.dart'; |
| import 'package:yaml/yaml.dart' show loadYaml; |
| |
| main() async { |
| String analyzerPkgPath = normalize(join(pkgRoot.packageRoot, 'analyzer')); |
| String frontEndPkgPath = normalize(join(pkgRoot.packageRoot, 'front_end')); |
| |
| Map<dynamic, dynamic> messagesYaml = loadYaml( |
| new File(join(frontEndPkgPath, 'messages.yaml')).readAsStringSync()); |
| String errorConverterSource = new File(join(analyzerPkgPath, |
| joinAll(posix.split('lib/src/fasta/error_converter.dart')))) |
| .readAsStringSync(); |
| String syntacticErrorsSource = new File(join(analyzerPkgPath, |
| joinAll(posix.split('lib/src/dart/error/syntactic_errors.dart')))) |
| .readAsStringSync(); |
| String parserSource = new File(join(frontEndPkgPath, |
| joinAll(posix.split('lib/src/fasta/parser/parser.dart')))) |
| .readAsStringSync(); |
| |
| final codeGenerator = new _SyntacticErrorGenerator( |
| messagesYaml, errorConverterSource, syntacticErrorsSource, parserSource); |
| |
| await GeneratedContent.generateAll(analyzerPkgPath, <GeneratedContent>[ |
| new GeneratedFile('lib/src/dart/error/syntactic_errors.g.dart', |
| (String pkgPath) async { |
| codeGenerator.generateFormatCode(); |
| return codeGenerator.out.toString(); |
| }), |
| ]); |
| |
| codeGenerator |
| ..checkForManualChanges() |
| ..printSummary(); |
| } |
| |
| const invalidAnalyzerCode = """ |
| Error: Expected the text in the 'analyzerCode:' field to contain |
| the name of the class containing the error |
| and the name of the error separated by a `.` |
| (e.g. ParserErrorCode.EQUALITY_CANNOT_BE_EQUALITY_OPERAND). |
| """; |
| |
| const shouldRunFastaGenerateMessagesFirst = """ |
| Error: After modifying message.yaml, run this first: |
| pkg/front_end/tool/fasta generate-messages |
| """; |
| |
| /// Return an entry containing 2 strings, |
| /// the name of the class containing the error and the name of the error, |
| /// or throw an exception if 'analyzerCode:' field is invalid. |
| List<String> nameForEntry(Map entry) { |
| final analyzerCode = entry['analyzerCode']; |
| if (analyzerCode is String) { |
| if (!analyzerCode.startsWith('ParserErrorCode.')) { |
| throw invalidAnalyzerCode; |
| } |
| List<String> name = analyzerCode.split('.'); |
| if (name.length != 2 || name[1].isEmpty) { |
| throw invalidAnalyzerCode; |
| } |
| return name; |
| } |
| throw invalidAnalyzerCode; |
| } |
| |
| class _SyntacticErrorGenerator { |
| final Map messagesYaml; |
| final String errorConverterSource; |
| final String syntacticErrorsSource; |
| final String parserSource; |
| final translatedEntries = <Map>[]; |
| final translatedFastaErrorCodes = new Set<String>(); |
| final out = new StringBuffer(''' |
| // |
| // THIS FILE IS GENERATED. DO NOT EDIT. |
| // |
| // Instead modify 'pkg/front_end/messages.yaml' and run |
| // 'dart pkg/analyzer/tool/messages/generate.dart' to update. |
| |
| part of 'syntactic_errors.dart'; |
| |
| '''); |
| |
| _SyntacticErrorGenerator(this.messagesYaml, this.errorConverterSource, |
| this.syntacticErrorsSource, this.parserSource); |
| |
| void checkForManualChanges() { |
| // Check for ParserErrorCodes that could be removed from |
| // error_converter.dart now that those ParserErrorCodes are auto generated. |
| int converterCount = 0; |
| for (Map entry in translatedEntries) { |
| final name = nameForEntry(entry); |
| final errorCode = name[1]; |
| if (errorConverterSource.contains('"$errorCode"')) { |
| if (converterCount == 0) { |
| print(''); |
| print('The following ParserErrorCodes could be removed' |
| ' from error_converter.dart:'); |
| } |
| print(' $errorCode'); |
| ++converterCount; |
| } |
| } |
| if (converterCount > 3) { |
| print(' $converterCount total'); |
| } |
| |
| // Check that the public ParserErrorCodes have been updated |
| // to reference the generated codes. |
| int publicCount = 0; |
| for (Map entry in translatedEntries) { |
| final name = nameForEntry(entry); |
| final errorCode = name[1]; |
| if (!syntacticErrorsSource.contains('_$errorCode')) { |
| if (publicCount == 0) { |
| print(''); |
| print('The following ParserErrorCodes should be updated' |
| ' in syntactic_errors.dart'); |
| print('to reference the associated generated error code:'); |
| } |
| print(' static const ParserErrorCode $errorCode = _$errorCode;'); |
| ++publicCount; |
| } |
| } |
| |
| // Fail if there are manual changes to be made, but do so |
| // in a fire and forget manner so that the files are still generated. |
| if (converterCount > 0 || publicCount > 0) { |
| print(''); |
| throw 'Error: missing manual code changes'; |
| } |
| } |
| |
| void generateErrorCodes() { |
| final sortedErrorCodes = <String>[]; |
| final entryMap = <String, Map>{}; |
| for (var entry in translatedEntries) { |
| final name = nameForEntry(entry); |
| final errorCode = name[1]; |
| sortedErrorCodes.add(errorCode); |
| if (entryMap[errorCode] == null) { |
| entryMap[errorCode] = entry; |
| } else { |
| throw 'Error: Duplicate error code $errorCode'; |
| } |
| } |
| sortedErrorCodes.sort(); |
| for (var errorCode in sortedErrorCodes) { |
| final entry = entryMap[errorCode]; |
| final className = nameForEntry(entry)[0]; |
| out.writeln(); |
| out.writeln('const $className _$errorCode ='); |
| out.writeln('const $className('); |
| out.writeln("'$errorCode',"); |
| out.writeln('r"${entry['template']}"'); |
| final tip = entry['tip']; |
| if (tip is String) { |
| out.writeln(',correction: "$tip"'); |
| } |
| out.writeln(');'); |
| } |
| } |
| |
| void generateFastaAnalyzerErrorCodeList() { |
| final sorted = new List<Map>(translatedEntries.length); |
| for (var entry in translatedEntries) { |
| var index = entry['index']; |
| if (index is int && index >= 1 && index <= sorted.length) { |
| if (sorted[index - 1] == null) { |
| sorted[index - 1] = entry; |
| continue; |
| } |
| } |
| throw shouldRunFastaGenerateMessagesFirst; |
| } |
| out.writeln('final fastaAnalyzerErrorCodes = <ErrorCode>[null,'); |
| for (var entry in sorted) { |
| List<String> name = nameForEntry(entry); |
| out.writeln('_${name[1]},'); |
| } |
| out.writeln('];'); |
| } |
| |
| void generateFormatCode() { |
| messagesYaml.forEach((name, entry) { |
| if (entry is Map) { |
| if (entry['index'] is int && entry['analyzerCode'] is String) { |
| translatedFastaErrorCodes.add(name); |
| translatedEntries.add(entry); |
| } |
| } |
| }); |
| generateFastaAnalyzerErrorCodeList(); |
| generateErrorCodes(); |
| } |
| |
| void printSummary() { |
| // Build a map of error message to ParserErrorCode |
| final messageToName = <String, String>{}; |
| for (ErrorCode errorCode in errorCodeValues) { |
| if (errorCode is ParserErrorCode) { |
| String message = |
| errorCode.message.replaceAll(new RegExp(r'\{\d+\}'), ''); |
| messageToName[message] = errorCode.name; |
| } |
| } |
| |
| String messageFromEntryTemplate(Map entry) { |
| String template = entry['template']; |
| String message = template.replaceAll(new RegExp(r'#\w+'), ''); |
| return message; |
| } |
| |
| // Remove entries that have already been translated |
| for (Map entry in translatedEntries) { |
| messageToName.remove(messageFromEntryTemplate(entry)); |
| } |
| |
| // Print the # of autogenerated ParserErrorCodes. |
| print('${translatedEntries.length} of ${messageToName.length}' |
| ' ParserErrorCodes generated.'); |
| |
| // List the ParserErrorCodes that could easily be auto generated |
| // but have not been already. |
| final analyzerToFasta = new Map<String, List<String>>(); |
| messagesYaml.forEach((fastaName, entry) { |
| if (entry is Map) { |
| final analyzerName = messageToName[messageFromEntryTemplate(entry)]; |
| if (analyzerName != null) { |
| analyzerToFasta |
| .putIfAbsent(analyzerName, () => <String>[]) |
| .add(fastaName); |
| } |
| } |
| }); |
| if (analyzerToFasta.isNotEmpty) { |
| print(''); |
| print('The following ParserErrorCodes could be auto generated:'); |
| for (String analyzerName in analyzerToFasta.keys.toList()..sort()) { |
| List<String> fastaNames = analyzerToFasta[analyzerName]; |
| if (fastaNames.length == 1) { |
| print(' $analyzerName = ${fastaNames.first}'); |
| } else { |
| print(' $analyzerName = $fastaNames'); |
| } |
| } |
| if (analyzerToFasta.length > 3) { |
| print(' ${analyzerToFasta.length} total'); |
| } |
| } |
| |
| // List error codes in the parser that have not been translated. |
| final untranslatedFastaErrorCodes = new Set<String>(); |
| Token token = scanString(parserSource).tokens; |
| while (!token.isEof) { |
| if (token.isIdentifier) { |
| String fastaErrorCode; |
| String lexeme = token.lexeme; |
| if (lexeme.length > 7) { |
| if (lexeme.startsWith('message')) { |
| fastaErrorCode = lexeme.substring(7); |
| } else if (lexeme.length > 8) { |
| if (lexeme.startsWith('template')) { |
| fastaErrorCode = lexeme.substring(8); |
| } |
| } |
| } |
| if (fastaErrorCode != null && |
| !translatedFastaErrorCodes.contains(fastaErrorCode)) { |
| untranslatedFastaErrorCodes.add(fastaErrorCode); |
| } |
| } |
| token = token.next; |
| } |
| if (untranslatedFastaErrorCodes.isNotEmpty) { |
| print(''); |
| print('The following error codes in the parser are not auto generated:'); |
| final sorted = untranslatedFastaErrorCodes.toList()..sort(); |
| for (String fastaErrorCode in sorted) { |
| String analyzerCode = ''; |
| String template = ''; |
| var entry = messagesYaml[fastaErrorCode]; |
| if (entry is Map) { |
| if (entry['index'] is! int && entry['analyzerCode'] is String) { |
| analyzerCode = entry['analyzerCode']; |
| template = entry['template']; |
| } |
| } |
| print(' ${fastaErrorCode.padRight(30)} --> $analyzerCode' |
| '\n $template'); |
| } |
| } |
| } |
| } |