blob: 8f16842f6b1e795e46ee86726c43b5aca3b1e0a4 [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 'dart:io' show File, Platform;
import 'package:_fe_analyzer_shared/src/scanner/characters.dart'
show $A, $MINUS, $a, $z;
import 'package:_fe_analyzer_shared/src/sdk/allowed_experiments.dart';
import 'package:dart_style/dart_style.dart' show DartFormatter;
import 'package:yaml/yaml.dart' show YamlMap, loadYaml;
main(List<String> arguments) {
new File.fromUri(computeCfeGeneratedFile())
.writeAsStringSync(generateCfeFile(), flush: true);
new File.fromUri(computeKernelGeneratedFile())
.writeAsStringSync(generateKernelFile(), flush: true);
}
Uri computeCfeGeneratedFile() {
return Platform.script
.resolve("../../lib/src/api_prototype/experimental_flags_generated.dart");
}
Uri computeKernelGeneratedFile() {
return Platform.script
.resolve("../../../kernel/lib/default_language_version.dart");
}
Uri computeYamlFile() {
return Platform.script
.resolve("../../../../tools/experimental_features.yaml");
}
Uri computeAllowListFile() {
return Platform.script
.resolve("../../../../sdk/lib/_internal/allowed_experiments.json");
}
String generateKernelFile() {
Uri yamlFile = computeYamlFile();
Map<dynamic, dynamic> yaml =
loadYaml(new File.fromUri(yamlFile).readAsStringSync());
int currentVersionMajor;
int currentVersionMinor;
{
String currentVersion = getAsVersionNumberString(yaml['current-version']);
List<String> split = currentVersion.split(".");
currentVersionMajor = int.parse(split[0]);
currentVersionMinor = int.parse(split[1]);
}
StringBuffer sb = new StringBuffer();
sb.write('''
// Copyright (c) 2020, 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
// 'dart pkg/front_end/tool/fasta.dart generate-experimental-flags' to update.
import "ast.dart";
Version defaultLanguageVersion = const Version($currentVersionMajor, $currentVersionMinor);
''');
return new DartFormatter().format("$sb");
}
String generateCfeFile() {
Uri yamlFile = computeYamlFile();
Map<dynamic, dynamic> yaml =
loadYaml(new File.fromUri(yamlFile).readAsStringSync());
int currentVersionMajor;
int currentVersionMinor;
{
String currentVersion = getAsVersionNumberString(yaml['current-version']);
List<String> split = currentVersion.split(".");
currentVersionMajor = int.parse(split[0]);
currentVersionMinor = int.parse(split[1]);
}
StringBuffer sb = new StringBuffer();
sb.write('''
// Copyright (c) 2020, 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
// 'dart pkg/front_end/tool/fasta.dart generate-experimental-flags' to update.
part of 'experimental_flags.dart';
''');
Map<String, dynamic> features = {};
Map<dynamic, dynamic> yamlFeatures = yaml['features'];
for (MapEntry<dynamic, dynamic> entry in yamlFeatures.entries) {
String category = entry.value["category"] ?? "language";
if (category != "language" && category != "CFE") {
// Skip a feature with a category that's not language or CFE.
// In the future we might want to generate different code for different
// things.
continue;
}
features[entry.key] = entry.value;
}
List<String> keys = features.keys.toList()..sort();
sb.write('''
enum ExperimentalFlag {
''');
for (String key in keys) {
sb.writeln(' ${keyToIdentifier(key)},');
}
sb.write('''
}
''');
for (String key in keys) {
int major;
int minor;
String enabledIn =
getAsVersionNumberString((features[key] as YamlMap)['enabledIn']);
if (enabledIn == null) {
major = currentVersionMajor;
minor = currentVersionMinor;
} else {
List<String> split = enabledIn.split(".");
major = int.parse(split[0]);
minor = int.parse(split[1]);
}
sb.writeln(' const Version enable'
'${keyToIdentifier(key, upperCaseFirst: true)}'
'Version = const Version($major, $minor);');
}
sb.write('''
ExperimentalFlag parseExperimentalFlag(String flag) {
switch (flag) {
''');
for (String key in keys) {
sb.writeln(' case "$key":');
sb.writeln(' return ExperimentalFlag.${keyToIdentifier(key)};');
}
sb.write(''' }
return null;
}
const Map<ExperimentalFlag, bool> defaultExperimentalFlags = {
''');
for (String key in keys) {
bool expired = (features[key] as YamlMap)['expired'];
bool shipped = (features[key] as YamlMap)['enabledIn'] != null;
sb.writeln(' ExperimentalFlag.${keyToIdentifier(key)}: ${shipped},');
if (shipped) {
if (expired == false) {
throw 'Cannot mark shipped feature as "expired: false"';
}
}
}
sb.write('''
};
const Map<ExperimentalFlag, bool> expiredExperimentalFlags = {
''');
for (String key in keys) {
bool expired = (features[key] as YamlMap)['expired'] == true;
sb.writeln(' ExperimentalFlag.${keyToIdentifier(key)}: ${expired},');
}
sb.write('''
};
const Map<ExperimentalFlag, Version> experimentEnabledVersion = {
''');
for (String key in keys) {
int major;
int minor;
String enabledIn =
getAsVersionNumberString((features[key] as YamlMap)['enabledIn']);
if (enabledIn != null) {
List<String> split = enabledIn.split(".");
major = int.parse(split[0]);
minor = int.parse(split[1]);
} else {
major = currentVersionMajor;
minor = currentVersionMinor;
}
sb.writeln(' ExperimentalFlag.${keyToIdentifier(key)}: '
'const Version($major, $minor),');
}
sb.write('''
};
const Map<ExperimentalFlag, Version> experimentReleasedVersion = {
''');
for (String key in keys) {
int major;
int minor;
String enabledIn =
getAsVersionNumberString((features[key] as YamlMap)['enabledIn']);
String experimentalReleaseVersion = getAsVersionNumberString(
(features[key] as YamlMap)['experimentalReleaseVersion']);
if (experimentalReleaseVersion != null) {
List<String> split = experimentalReleaseVersion.split(".");
major = int.parse(split[0]);
minor = int.parse(split[1]);
} else if (enabledIn != null) {
List<String> split = enabledIn.split(".");
major = int.parse(split[0]);
minor = int.parse(split[1]);
} else {
major = currentVersionMajor;
minor = currentVersionMinor;
}
sb.writeln(' ExperimentalFlag.${keyToIdentifier(key)}: '
'const Version($major, $minor),');
}
sb.write('''
};
''');
Uri allowListFile = computeAllowListFile();
AllowedExperiments allowedExperiments = parseAllowedExperiments(
new File.fromUri(allowListFile).readAsStringSync());
sb.write('''
const AllowedExperimentalFlags defaultAllowedExperimentalFlags =
const AllowedExperimentalFlags(
''');
sb.writeln('sdkDefaultExperiments: {');
for (String sdkDefaultExperiment
in allowedExperiments.sdkDefaultExperiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(sdkDefaultExperiment)},');
}
sb.writeln('},');
sb.writeln('sdkLibraryExperiments: {');
allowedExperiments.sdkLibraryExperiments
.forEach((String library, List<String> experiments) {
sb.writeln('"$library": {');
for (String experiment in experiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(experiment)},');
}
sb.writeln('},');
});
sb.writeln('},');
sb.writeln('packageExperiments: {');
allowedExperiments.packageExperiments
.forEach((String package, List<String> experiments) {
sb.writeln('"$package": {');
for (String experiment in experiments) {
sb.writeln('ExperimentalFlag.${keyToIdentifier(experiment)},');
}
sb.writeln('},');
});
sb.writeln('});');
return new DartFormatter().format("$sb");
}
keyToIdentifier(String key, {bool upperCaseFirst = false}) {
StringBuffer identifier = StringBuffer();
bool first = true;
for (int index = 0; index < key.length; ++index) {
int code = key.codeUnitAt(index);
if (code == $MINUS) {
++index;
code = key.codeUnitAt(index);
if ($a <= code && code <= $z) {
code = code - $a + $A;
}
}
if (first && upperCaseFirst && $a <= code && code <= $z) {
code = code - $a + $A;
}
first = false;
identifier.writeCharCode(code);
}
return identifier.toString();
}
String getAsVersionNumberString(dynamic value) {
if (value == null) return null;
if (value is String) return value;
if (value is double) return "$value";
throw "Unexpected value: $value (${value.runtimeType})";
}