// 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. // @dart=2.9 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 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 yaml = loadYaml(new File.fromUri(yamlFile).readAsStringSync()); int currentVersionMajor; int currentVersionMinor; { String currentVersion = getAsVersionNumberString(yaml['current-version']); List 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 yamlFile = computeYamlFile(); Map yaml = loadYaml(new File.fromUri(yamlFile).readAsStringSync()); int currentVersionMajor; int currentVersionMinor; { String currentVersion = getAsVersionNumberString(yaml['current-version']); List 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 features = {}; Map yamlFeatures = yaml['features']; for (MapEntry 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 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 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 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 expiredExperimentalFlags = { '''); for (String key in keys) { bool expired = (features[key] as YamlMap)['expired'] == true; sb.writeln(' ExperimentalFlag.${keyToIdentifier(key)}: ${expired},'); } sb.write(''' }; const Map experimentEnabledVersion = { '''); for (String key in keys) { int major; int minor; String enabledIn = getAsVersionNumberString((features[key] as YamlMap)['enabledIn']); if (enabledIn != null) { List 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 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 split = experimentalReleaseVersion.split("."); major = int.parse(split[0]); minor = int.parse(split[1]); } else if (enabledIn != null) { List 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 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 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})"; }