// Optional language features -- the WebAssembly proposals a module may opt in
// to.
//
// Ported from wax/src/lib-utils/feature.ml. A feature is off unless the command
// line (`-X name`) or the module itself (`#![feature = "name"]`) turns it on,
// and a `Set` records which ones a module actually EXERCISED, so a binary can
// carry the declaration forward in its `target_features` section.
//
// The three states matter and are not two: a feature can be off by default, or
// off because someone said `-X name=off`. A module declaring a feature against
// the second is a conflict to report; against the first it is just an opt-in.
///|
/// A proposal this toolchain knows about.
pub(all) enum Feature {
/// Exact reference types, descriptor structs, and their instructions.
CustomDescriptors
/// Grouping a module's imports under one module name in the binary import
/// section.
CompactImportSection
} derive(Eq, Compare, Debug)
///|
/// Every known feature, in the order everything reports them.
pub let all : Array[Feature] = [CustomDescriptors, CompactImportSection]
///|
/// The command-line and diagnostic name.
pub fn Feature::name(self : Feature) -> String {
match self {
CustomDescriptors => "custom-descriptors"
CompactImportSection => "compact-import-section"
}
}
///|
pub fn Feature::description(self : Feature) -> String {
match self {
CustomDescriptors =>
"Custom Descriptors proposal: exact reference types, descriptor structs, and the associated instructions."
CompactImportSection =>
"Compact Import Section proposal: group a module's imports under one module name in the binary import section."
}
}
///|
/// Whether the feature is on when nobody has said otherwise.
///
/// Every proposal here is experimental, so: no.
pub fn Feature::enabled_by_default(self : Feature) -> Bool {
match self {
CustomDescriptors => false
CompactImportSection => false
}
}
///|
pub fn of_name(s : String) -> Feature? {
for f in all {
if f.name() == s {
return Some(f)
}
}
None
}
///|
/// A resolved configuration: what is enabled, and what has been used.
///
/// One per module, since usage is per-module.
pub struct Set {
/// The features on, whether by default, by `-X`, or by declaration.
enabled : @sorted_set.SortedSet[Feature]
/// The features an `-X name=off` explicitly turned off, which a module
/// declaring one of them CONFLICTS with -- unlike one merely off by default.
explicitly_off : @sorted_set.SortedSet[Feature]
used : @sorted_set.SortedSet[Feature]
}
///|
/// Take the defaults and apply `specs` over them; later entries win.
pub fn configure(specs : Array[(Feature, Bool)]) -> Set {
let enabled : @sorted_set.SortedSet[Feature] = @sorted_set.SortedSet([])
let explicitly_off : @sorted_set.SortedSet[Feature] = @sorted_set.SortedSet([])
for f in all {
if f.enabled_by_default() {
enabled.add(f)
}
}
for spec in specs {
let (f, on) = spec
if on {
enabled.add(f)
explicitly_off.remove(f)
} else {
enabled.remove(f)
explicitly_off.add(f)
}
}
{ enabled, explicitly_off, used: @sorted_set.SortedSet([]) }
}
///|
/// The process-wide configuration, installed once from the command line -- the
/// same shape as the warning policy.
let global_specs : Ref[Array[(Feature, Bool)]] = @ref.new([])
///|
pub fn set_config(specs : Array[(Feature, Bool)]) -> Unit {
global_specs.val = specs
}
///|
/// A fresh set from the installed configuration, nothing used yet.
pub fn default() -> Set {
configure(global_specs.val)
}
///|
pub fn Set::is_enabled(self : Set, f : Feature) -> Bool {
self.enabled.contains(f)
}
///|
/// Was this feature turned off explicitly, rather than merely being off?
pub fn Set::explicitly_disabled(self : Set, f : Feature) -> Bool {
self.explicitly_off.contains(f)
}
///|
/// Turn a feature on because the module declares it.
///
/// Not `declare`: that is a MoonBit keyword.
///
/// Check `explicitly_disabled` first: declaring what the command line turned
/// off is a conflict, not an override.
pub fn Set::declare_feature(self : Set, f : Feature) -> Unit {
self.enabled.add(f)
}
///|
/// Record that a feature is used -- whether or not it is enabled.
pub fn Set::mark_used(self : Set, f : Feature) -> Unit {
self.used.add(f)
}
///|
/// What has been used, in `all` order so output is stable.
pub fn Set::used_features(self : Set) -> Array[Feature] {
all.filter(f => self.used.contains(f))
}
///|
/// Parse a command-line spec: `NAME`, `NAME=on` or `NAME=off`.
pub fn parse_spec(s : String) -> Result[(Feature, Bool), String] {
let (nm, value) = match s.find("=") {
None => (s, "on")
Some(i) => (s[:i].to_owned(), s[i + 1:].to_owned())
}
match of_name(nm) {
None => {
let known = all.map(f => f.name()).join(", ")
Err("Unknown feature '\{nm}'. Known features: \{known}.")
}
Some(f) =>
match value {
"on" | "true" | "yes" => Ok((f, true))
"off" | "false" | "no" => Ok((f, false))
_ =>
Err(
"Malformed feature spec '\{s}'; expected NAME, NAME=on, or NAME=off.",
)
}
}
}