# Experimental Features & API |<!-- -->|<!-- -->| |-------:|:-------| |Creator |@Moderocky| |Type |Feature| |Status |Pull Request| |Created |04/24| ## Summary Toggleable feature-flags through a `using <feature>` structure for individual scripts to enable optional, experimental or alternative behaviour at parse time. ## Introduction ### Goals - To provide a way to enable syntax and features only in certain scripts. - To be able to ship experimental changes without affecting users. - The means to offer new and alternative features without unwanted breaking changes. ### Non-Goals <!-- Optional --> - It is not a goal to require all new (or existing) syntax to be specifically enabled through a feature flag. - This pull request does not add any experimental features itself. ### Motivations Skript has difficulties with breaking changes. Breaking changes make the userbase reluctant to update, even if it means missing out on speed or security improvements. Currently, there is no concept of an 'opt-in' feature apart from specific config options. ## Description This proposal adds API for 'experimental features' both within the Skript developer API and the language itself. These features exist as 'flags' on a script that are detectable as either present or not present, and so parse-time behaviour can be configured for this (such as choosing which version of a syntax to init, or whether to allow a particular matched pattern to be used). ### The Purpose of Experimental Features In essence, features allow Skript (or addons) to enable syntax (or syntax variations) only within particular files that opt in to using them. An obvious use case of this is to ship testing (literally experimental) syntax in stable versions, that users who want to test (or need the functionality of) the unstable syntax can opt into, without affecting anybody who doesn't want to be at risk from it. This may give a potential avenue for shipping newly-released Minecraft version features in a patch version of Skript, by gating them behind a temporary toggle (e.g. `1.21 syntax`). Another use of opt-in features is to prepare users for upcoming breaking changes (or, alternatively, giving users who *cannot* update access to the old functionality). For example, a feature flag could be used to enable case [in]sensitivity for a particular script. The third and final intended purpose is for offering experimental changes for the purpose of getting feedback on their use. These changes can be shipped in a version, behind an opt-in flag (e.g. `2.9.2 big head syntax`) that users are able to test and provide feedback on, with the understanding that the functionality is likely to change (or even be removed) in future versions. ### Using Features in Scripts Scripts can enable an experimental/optional feature with the new `using <feature>` simple structure, which should be placed at the top of the file. ```cpp= using example feature on script load: ... ``` This enables the feature (`example feature`) in -- and only in -- the current script. A script may use multiple features. ```cpp= using 1.21 syntax using threadlocal variables using strict lists using example feature using my cool addon feature ``` There is no penalty for using a feature, even if no feature by that name exists. ```cpp= using foobar fake feature ``` In the above example, imagine nothing registers a `foobar fake feature`. No error is reported for 'using' a non-existent feature (although the user will be warned that no feature by that name was registered) and parsing will continue as normal. **Implementation note**: this no-failure behaviour was specifically done for forwards compatibility; Skript might initially register an experimental feature for X, but when X eventually becomes standard syntax then the feature is no longer needed and can be removed without breaking anything using it. A feature's presence can be detected from within a script itself, although only within the current script. ```cpp= using example feature on script load: if the script is using "example feature": broadcast "wow, a feature!" if the script is using {something}: broadcast "wow, another feature" ``` ### Using Features in the Skript API All new API can be found in the `org.skriptlang.skript.lang.experiment` package. #### Creating a Feature Flag Opt-in features are registered as an `Experiment`. An `Experiment` needs to provide: 1. A 'code name' (the simple name of the feature for error triage, e.g. `example feature`) 2. Its life cycle phase, which is used for providing warnings or recommendations to the user: - `EXPERIMENTAL` for opt-in features that are likely to undergo changes or become the default (e.g. new version syntax, testing changes, etc.) - `STABLE` for opt-in features that are planned to exist semi-permanently (e.g. case sensitivity, strict whitespace, backslash escapes in strings, etc.) - `MAINSTREAM` for legacy features that have now become default behaviour; users will be warned they no longer need to enable these - `DEPRECATED` for features that are dangerous or expected to be removed; users will be warned against using these 3. Its `SkriptPattern` matcher, usually through the form of several String patterns. Third-party addons or extensions will need to create implementations of this class and, ideally save them in a constant or some kind of enum. A simple factory method `Experiment.constant(codeName, phase, patterns...)` is provided to make this easier to use. Skript itself has a `Feature` enum for simplicity: ```java EXAMPLE_FEATURE("example feature", LifeCycle.STABLE) FOO("foo", LifeCycle.STABLE, "foo[bar]", "food", "foot[ ]ball") ``` #### Registering the Experimental Feature Features can be registered with Skript's new `ExperimentManager` at the same time as syntax registration. The experiment manager is available during run-time as well (as we pre-emptively expect this may be used by addons that modify syntax, such as skript-reflect) although, obviously, features will not be available to a script after it has already been parsed. The experiment manager instance is obtained from `Skript.experiments()` and the flag is registered using `manager.register(addonInstance, experiment)`. ```java static final Experiment MY_FEATURE = Experiment.constant("my feature", LifeCycle.STABLE, "my [cool] feature"); void registerMyThing(SkriptAddon addon) { Skript.experiments().register(addon, MY_FEATURE); } ``` This will then be available to scripts: ```cpp= using my feature using my cool feature ``` #### Detecting an Experimental Feature Within syntax classes, during parse time, the presence of an experimental feature can be detected. A simple method is provided for checking: `this.hasExperiment(feature)` ```java= public boolean init(Expression<?>[] expressions, int pattern, Kleenean delayed, ParseResult result) { if (!this.hasExperiment(MY_FEATURE)) return false; // do your syntax stuff here... return true; } ``` Please note this is only (reliably) available during parse time. The value can be stored for use during run-time. ### The Unknown Feature If a user enters a non-existent feature, e.g. ```cpp= using foobar fake feature ``` Skript will create an `Experiment` with the code-name `foobar fake feature` and the `UNKNOWN` life cycle phase. This is registered to the script as a real feature would, and is detectable. The reasoning behind this behaviour is 1) to attempt support for back-references in features registered after parsing time (since you *can* test for a feature flag by name) and 2) in case any future use (or third party extension) wants to use the feature flag for passing unknown, unregistered data in the `using` structure. ## Conclusion Experimental feature flags ought to provide new ways that syntax and features can be shipped for testing without users taking on any extra risk. It should also be a way for us to add more syntax without creating problems or conflicts, and allow users to opt into some breaking changes. ### Addendum: Failure Standards No proposal is complete without failure standards; the proposal system can be deemed to have failed if, in a year after publication: 1. Feature flags are not improving the development cycle or the availability of syntax. 2. Breaking changes are getting past feature flags into patch versions.