generate kernel experimental flags

Change-Id: I9430d95c012b752bc4ca0530d79252b8b032a26b
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/104841
Reviewed-by: Kevin Millikin <kmillikin@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
Auto-Submit: Dan Rubel <danrubel@google.com>
diff --git a/pkg/front_end/lib/src/api_prototype/experimental_flags.dart b/pkg/front_end/lib/src/api_prototype/experimental_flags.dart
index f91b17d..6428f9b 100644
--- a/pkg/front_end/lib/src/api_prototype/experimental_flags.dart
+++ b/pkg/front_end/lib/src/api_prototype/experimental_flags.dart
@@ -2,7 +2,10 @@
 // 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.
 
-// TODO(askesc): Generate this file from a flag specification.
+// NOTE: THIS FILE IS GENERATED. DO NOT EDIT.
+//
+// Instead modify 'tools/experimental_features.yaml' and run
+// 'pkg/front_end/tool/fasta generate-experimental-flags' to update.
 
 enum ExperimentalFlag {
   constantUpdate2018,
diff --git a/pkg/front_end/tool/_fasta/generate_experimental_flags.dart b/pkg/front_end/tool/_fasta/generate_experimental_flags.dart
new file mode 100644
index 0000000..355836a
--- /dev/null
+++ b/pkg/front_end/tool/_fasta/generate_experimental_flags.dart
@@ -0,0 +1,105 @@
+// 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 'dart:async';
+
+import 'dart:io';
+
+import 'dart:isolate';
+
+import 'package:yaml/yaml.dart' show YamlMap, loadYaml;
+
+import 'package:dart_style/dart_style.dart' show DartFormatter;
+
+import '../../lib/src/fasta/scanner/characters.dart' show $A, $MINUS, $a, $z;
+
+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/api_prototype/experimental_flags.dart'));
+}
+
+Future<String> generateMessagesFile() async {
+  Uri messagesFile =
+      Platform.script.resolve("../../../../tools/experimental_features.yaml");
+  Map<dynamic, dynamic> yaml =
+      loadYaml(await new File.fromUri(messagesFile).readAsStringSync());
+  StringBuffer sb = new StringBuffer();
+
+  sb.write('''
+// 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 'tools/experimental_features.yaml' and run
+// 'pkg/front_end/tool/fasta generate-experimental-flags' to update.
+''');
+
+  List<String> keys = yaml.keys.cast<String>().toList()..sort();
+
+  sb.write('''
+
+enum ExperimentalFlag {
+''');
+  for (var key in keys) {
+    sb.writeln('  ${keyToIdentifier(key)},');
+  }
+  sb.writeln('}');
+
+  sb.write('''
+
+ExperimentalFlag parseExperimentalFlag(String flag) {
+  switch (flag) {
+''');
+  for (var key in keys) {
+    sb.writeln('    case "$key":');
+    sb.writeln('     return ExperimentalFlag.${keyToIdentifier(key)};');
+  }
+  sb.write('''  }
+  return null;
+}
+''');
+
+  sb.write('''
+
+const Map<ExperimentalFlag, bool> defaultExperimentalFlags = {
+''');
+  for (var key in keys) {
+    bool shipped = (yaml[key] as YamlMap)['enabledIn'] != null;
+    sb.writeln('  ExperimentalFlag.${keyToIdentifier(key)}: ${shipped},');
+    if (shipped) {
+      var expired = (yaml[key] as YamlMap)['expired'];
+      if (expired == false) {
+        throw 'Cannot mark shipped feature as "expired: false"';
+      }
+    }
+  }
+  sb.writeln('};');
+
+  return new DartFormatter().format("$sb");
+}
+
+keyToIdentifier(String key) {
+  var identifier = StringBuffer();
+  for (int index = 0; index < key.length; ++index) {
+    var code = key.codeUnitAt(index);
+    if (code == $MINUS) {
+      ++index;
+      code = key.codeUnitAt(index);
+      if ($a <= code && code <= $z) {
+        code = code - $a + $A;
+      }
+    }
+    identifier.writeCharCode(code);
+  }
+  return identifier.toString();
+}
diff --git a/pkg/front_end/tool/_fasta/generate_experimental_flags_test.dart b/pkg/front_end/tool/_fasta/generate_experimental_flags_test.dart
new file mode 100644
index 0000000..81fdca0
--- /dev/null
+++ b/pkg/front_end/tool/_fasta/generate_experimental_flags_test.dart
@@ -0,0 +1,23 @@
+// 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.
+
+import "dart:io" show File;
+
+import "package:async_helper/async_helper.dart" show asyncTest;
+
+import "package:expect/expect.dart" show Expect;
+
+import "generate_experimental_flags.dart"
+    show computeGeneratedFile, generateMessagesFile;
+
+main() {
+  asyncTest(() async {
+    Uri generatedFile = await computeGeneratedFile();
+    String generated = await generateMessagesFile();
+    String actual = (await new File.fromUri(generatedFile).readAsString())
+        .replaceAll('\r\n', '\n');
+    Expect.stringEquals(
+        generated, actual, "${generatedFile.path} is out of date");
+  });
+}
diff --git a/pkg/front_end/tool/fasta b/pkg/front_end/tool/fasta
index 65b9ec4..aa970c3 100755
--- a/pkg/front_end/tool/fasta
+++ b/pkg/front_end/tool/fasta
@@ -55,6 +55,7 @@
     set -- "$@" "--config=${REPO_DIR}/pkg/front_end/testing.json"
     ;;
   generate-messages) SCRIPT="${TOOL_DIR}/generate_messages.dart";;
+  generate-experimental-flags) SCRIPT="${TOOL_DIR}/generate_experimental_flags.dart";;
   *)
     stop "'$1' isn't a valid subcommand."
     ;;