| // 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; |
| |
| 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; |
| |
| import '../../test/utils/io_utils.dart' show computeRepoDirUri; |
| |
| void main(List<String> arguments) { |
| final Uri repoDir = computeRepoDirUri(); |
| new File.fromUri(computeCfeGeneratedFile(repoDir)) |
| .writeAsStringSync(generateCfeFile(repoDir), flush: true); |
| new File.fromUri(computeKernelGeneratedFile(repoDir)) |
| .writeAsStringSync(generateKernelFile(repoDir), flush: true); |
| } |
| |
| Uri computeCfeGeneratedFile(Uri repoDir) { |
| return repoDir.resolve( |
| "pkg/front_end/lib/src/api_prototype/experimental_flags_generated.dart"); |
| } |
| |
| Uri computeKernelGeneratedFile(Uri repoDir) { |
| return repoDir.resolve("pkg/kernel/lib/default_language_version.dart"); |
| } |
| |
| Uri computeYamlFile(Uri repoDir) { |
| return repoDir.resolve("tools/experimental_features.yaml"); |
| } |
| |
| Uri computeAllowListFile(Uri repoDir) { |
| return repoDir.resolve("sdk/lib/_internal/allowed_experiments.json"); |
| } |
| |
| String generateKernelFile(Uri repoDir) { |
| Uri yamlFile = computeYamlFile(repoDir); |
| 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) 2021, 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 repoDir) { |
| Uri yamlFile = computeYamlFile(repoDir); |
| 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) 2021, 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(''' |
| |
| /// An experiment flag including its fixed properties. |
| class ExperimentalFlag { |
| /// The name of this flag as used in the --enable-experiment option. |
| final String name; |
| |
| /// `true` if this experimental feature is enabled by default. |
| /// |
| /// When `true`, the feature can still be disabled in individual libraries |
| /// with a language version below the [experimentEnabledVersion], and if not |
| /// [isExpired], the feature can also be disabled by using a 'no-' prefix |
| /// in the --enable-experiment option. |
| final bool isEnabledByDefault; |
| |
| /// `true` if this feature can no longer be changed using the |
| /// --enable-experiment option. |
| /// |
| /// Libraries can still opt out of the feature by using a language version |
| /// below the [experimentEnabledVersion]. |
| final bool isExpired; |
| final Version enabledVersion; |
| |
| /// The minimum version that supports this feature. |
| /// |
| /// If the feature is not enabled by default, this is the current language |
| /// version. |
| final Version experimentEnabledVersion; |
| |
| /// The minimum version that supports this feature in allowed libraries. |
| /// |
| /// Allowed libraries are specified in |
| /// |
| /// sdk/lib/_internal/allowed_experiments.json |
| final Version experimentReleasedVersion; |
| |
| const ExperimentalFlag( |
| {required this.name, |
| required this.isEnabledByDefault, |
| required this.isExpired, |
| required this.enabledVersion, |
| required this.experimentEnabledVersion, |
| required this.experimentReleasedVersion}); |
| '''); |
| for (String key in keys) { |
| String identifier = keyToIdentifier(key); |
| int enabledInMajor; |
| int enabledInMinor; |
| String? enabledIn = |
| getAsVersionNumberString((features[key] as YamlMap)['enabledIn']); |
| if (enabledIn == null) { |
| enabledInMajor = currentVersionMajor; |
| enabledInMinor = currentVersionMinor; |
| } else { |
| List<String> split = enabledIn.split("."); |
| enabledInMajor = int.parse(split[0]); |
| enabledInMinor = int.parse(split[1]); |
| } |
| bool? expired = (features[key] as YamlMap)['expired']; |
| bool shipped = (features[key] as YamlMap)['enabledIn'] != null; |
| if (shipped) { |
| if (expired == false) { |
| throw 'Cannot mark shipped feature "$key" as "expired: false"'; |
| } |
| } |
| int releaseMajor; |
| int releaseMinor; |
| String? experimentalReleaseVersion = getAsVersionNumberString( |
| (features[key] as YamlMap)['experimentalReleaseVersion']); |
| if (experimentalReleaseVersion != null) { |
| List<String> split = experimentalReleaseVersion.split("."); |
| releaseMajor = int.parse(split[0]); |
| releaseMinor = int.parse(split[1]); |
| } else if (enabledIn != null) { |
| List<String> split = enabledIn.split("."); |
| releaseMajor = int.parse(split[0]); |
| releaseMinor = int.parse(split[1]); |
| } else { |
| releaseMajor = currentVersionMajor; |
| releaseMinor = currentVersionMinor; |
| } |
| |
| sb.writeln(''' |
| static const ExperimentalFlag ${identifier} = |
| const ExperimentalFlag( |
| name: '$key', |
| isEnabledByDefault: $shipped, |
| isExpired: ${expired == true}, |
| enabledVersion: const Version($enabledInMajor, $enabledInMinor), |
| experimentEnabledVersion: const Version($enabledInMajor, $enabledInMinor), |
| experimentReleasedVersion: const Version($releaseMajor, $releaseMinor)); |
| '''); |
| } |
| sb.write(''' |
| } |
| '''); |
| |
| sb.write(''' |
| /// Interface for accessing the global state of experimental features. |
| class GlobalFeatures { |
| final Map<ExperimentalFlag, bool> explicitExperimentalFlags; |
| final AllowedExperimentalFlags? allowedExperimentalFlags; |
| final Map<ExperimentalFlag, bool>? defaultExperimentFlagsForTesting; |
| final Map<ExperimentalFlag, Version>? experimentEnabledVersionForTesting; |
| final Map<ExperimentalFlag, Version>? experimentReleasedVersionForTesting; |
| |
| GlobalFeatures(this.explicitExperimentalFlags, |
| {this.allowedExperimentalFlags, |
| this.defaultExperimentFlagsForTesting, |
| this.experimentEnabledVersionForTesting, |
| this.experimentReleasedVersionForTesting}); |
| |
| GlobalFeature _computeGlobalFeature(ExperimentalFlag flag) { |
| return new GlobalFeature( |
| flag, |
| isExperimentEnabled(flag, |
| defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting, |
| explicitExperimentalFlags: explicitExperimentalFlags)); |
| } |
| |
| LibraryFeature _computeLibraryFeature( |
| ExperimentalFlag flag, Uri canonicalUri, Version libraryVersion) { |
| return new LibraryFeature( |
| flag, |
| isExperimentEnabledInLibrary(flag, canonicalUri, |
| defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting, |
| explicitExperimentalFlags: explicitExperimentalFlags, |
| allowedExperimentalFlags: allowedExperimentalFlags), |
| getExperimentEnabledVersionInLibrary( |
| flag, canonicalUri, explicitExperimentalFlags, |
| allowedExperimentalFlags: allowedExperimentalFlags, |
| defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting, |
| experimentEnabledVersionForTesting: |
| experimentEnabledVersionForTesting, |
| experimentReleasedVersionForTesting: |
| experimentReleasedVersionForTesting), |
| isExperimentEnabledInLibraryByVersion( |
| flag, canonicalUri, libraryVersion, |
| defaultExperimentFlagsForTesting: defaultExperimentFlagsForTesting, |
| explicitExperimentalFlags: explicitExperimentalFlags, |
| allowedExperimentalFlags: allowedExperimentalFlags)); |
| } |
| '''); |
| for (String key in keys) { |
| String identifier = keyToIdentifier(key); |
| sb.write(''' |
| |
| GlobalFeature? _${identifier}; |
| GlobalFeature get ${identifier} => |
| _${identifier} ??= _computeGlobalFeature(ExperimentalFlag.${identifier}); |
| '''); |
| } |
| sb.write(''' |
| } |
| |
| /// Interface for accessing the state of experimental features within a |
| /// specific library. |
| class LibraryFeatures { |
| final GlobalFeatures globalFeatures; |
| final Uri canonicalUri; |
| final Version libraryVersion; |
| |
| LibraryFeatures(this.globalFeatures, this.canonicalUri, this.libraryVersion); |
| '''); |
| for (String key in keys) { |
| String identifier = keyToIdentifier(key); |
| sb.write(''' |
| |
| LibraryFeature? _${identifier}; |
| LibraryFeature get ${identifier} => _${identifier} ??= globalFeatures |
| ._computeLibraryFeature( |
| ExperimentalFlag.${identifier}, |
| canonicalUri, |
| libraryVersion); |
| '''); |
| } |
| sb.write(''' |
| } |
| '''); |
| |
| 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; |
| } |
| |
| final Map<ExperimentalFlag, bool> defaultExperimentalFlags = { |
| '''); |
| for (String key in keys) { |
| sb.writeln(''' |
| ExperimentalFlag.${keyToIdentifier(key)}: |
| ExperimentalFlag.${keyToIdentifier(key)}.isEnabledByDefault,'''); |
| } |
| sb.write(''' |
| }; |
| '''); |
| |
| Uri allowListFile = computeAllowListFile(repoDir); |
| 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"); |
| } |
| |
| String 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})"; |
| } |