18995750aSAlex Crichton //! Support for parsing Wasmtime's `-O`, `-W`, etc "option groups"
28995750aSAlex Crichton //!
38995750aSAlex Crichton //! This builds up a clap-derive-like system where there's ideally a single
48995750aSAlex Crichton //! macro `wasmtime_option_group!` which is invoked per-option which enables
58995750aSAlex Crichton //! specifying options in a struct-like syntax where all other boilerplate about
68995750aSAlex Crichton //! option parsing is contained exclusively within this module.
78995750aSAlex Crichton
812fc764dSXinzhao Xu use crate::{KeyValuePair, WasiNnGraph};
98995750aSAlex Crichton use clap::builder::{StringValueParser, TypedValueParser, ValueParserFactory};
108995750aSAlex Crichton use clap::error::{Error, ErrorKind};
112a5c141cSLudvig Liljenberg use serde::de::{self, Visitor};
12*133a0ef4SChris Fallin use std::path::PathBuf;
13*133a0ef4SChris Fallin use std::str::FromStr;
148995750aSAlex Crichton use std::time::Duration;
152a5c141cSLudvig Liljenberg use std::{fmt, marker};
16b11f8675SNick Fitzgerald use wasmtime::{Result, bail};
178995750aSAlex Crichton
1881f9efc0SVictor Adossi /// Characters which can be safely ignored while parsing numeric options to wasmtime
1981f9efc0SVictor Adossi const IGNORED_NUMBER_CHARS: [char; 1] = ['_'];
2081f9efc0SVictor Adossi
218995750aSAlex Crichton #[macro_export]
228995750aSAlex Crichton macro_rules! wasmtime_option_group {
238995750aSAlex Crichton (
24519808fcSAlex Crichton $(#[$attr:meta])*
258995750aSAlex Crichton pub struct $opts:ident {
268995750aSAlex Crichton $(
278995750aSAlex Crichton $(#[doc = $doc:tt])*
283ecb338eSNick Fitzgerald $(#[doc($doc_attr:meta)])?
292a5c141cSLudvig Liljenberg $(#[serde($serde_attr:meta)])*
308995750aSAlex Crichton pub $opt:ident: $container:ident<$payload:ty>,
318995750aSAlex Crichton )+
328995750aSAlex Crichton
338995750aSAlex Crichton $(
348995750aSAlex Crichton #[prefixed = $prefix:tt]
352a5c141cSLudvig Liljenberg $(#[serde($serde_attr2:meta)])*
368995750aSAlex Crichton $(#[doc = $prefixed_doc:tt])*
373ecb338eSNick Fitzgerald $(#[doc($prefixed_doc_attr:meta)])?
388995750aSAlex Crichton pub $prefixed:ident: Vec<(String, Option<String>)>,
398995750aSAlex Crichton )?
408995750aSAlex Crichton }
418995750aSAlex Crichton enum $option:ident {
428995750aSAlex Crichton ...
438995750aSAlex Crichton }
448995750aSAlex Crichton ) => {
458995750aSAlex Crichton #[derive(Default, Debug)]
46519808fcSAlex Crichton $(#[$attr])*
478995750aSAlex Crichton pub struct $opts {
488995750aSAlex Crichton $(
492a5c141cSLudvig Liljenberg $(#[serde($serde_attr)])*
503ecb338eSNick Fitzgerald $(#[doc($doc_attr)])?
518995750aSAlex Crichton pub $opt: $container<$payload>,
528995750aSAlex Crichton )+
538995750aSAlex Crichton $(
542a5c141cSLudvig Liljenberg $(#[serde($serde_attr2)])*
558995750aSAlex Crichton pub $prefixed: Vec<(String, Option<String>)>,
568995750aSAlex Crichton )?
578995750aSAlex Crichton }
588995750aSAlex Crichton
59ba4e22bcSAlex Crichton #[derive(Clone, PartialEq)]
6045b60bd6SAlex Crichton #[expect(non_camel_case_types, reason = "macro-generated code")]
618995750aSAlex Crichton enum $option {
628995750aSAlex Crichton $(
638995750aSAlex Crichton $opt($payload),
648995750aSAlex Crichton )+
658995750aSAlex Crichton $(
668995750aSAlex Crichton $prefixed(String, Option<String>),
678995750aSAlex Crichton )?
688995750aSAlex Crichton }
698995750aSAlex Crichton
708995750aSAlex Crichton impl $crate::opt::WasmtimeOption for $option {
718995750aSAlex Crichton const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[
728995750aSAlex Crichton $(
738995750aSAlex Crichton $crate::opt::OptionDesc {
748995750aSAlex Crichton name: $crate::opt::OptName::Name(stringify!($opt)),
758995750aSAlex Crichton parse: |_, s| {
768995750aSAlex Crichton Ok($option::$opt(
778995750aSAlex Crichton $crate::opt::WasmtimeOptionValue::parse(s)?
788995750aSAlex Crichton ))
798995750aSAlex Crichton },
808995750aSAlex Crichton val_help: <$payload as $crate::opt::WasmtimeOptionValue>::VAL_HELP,
818995750aSAlex Crichton docs: concat!($($doc, "\n",)*),
828995750aSAlex Crichton },
838995750aSAlex Crichton )+
848995750aSAlex Crichton $(
858995750aSAlex Crichton $crate::opt::OptionDesc {
868995750aSAlex Crichton name: $crate::opt::OptName::Prefix($prefix),
878995750aSAlex Crichton parse: |name, val| {
888995750aSAlex Crichton Ok($option::$prefixed(
898995750aSAlex Crichton name.to_string(),
908995750aSAlex Crichton val.map(|v| v.to_string()),
918995750aSAlex Crichton ))
928995750aSAlex Crichton },
938995750aSAlex Crichton val_help: "[=val]",
948995750aSAlex Crichton docs: concat!($($prefixed_doc, "\n",)*),
958995750aSAlex Crichton },
968995750aSAlex Crichton )?
978995750aSAlex Crichton ];
988995750aSAlex Crichton }
998995750aSAlex Crichton
100ba4e22bcSAlex Crichton impl core::fmt::Display for $option {
101ba4e22bcSAlex Crichton fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
102ba4e22bcSAlex Crichton match self {
103ba4e22bcSAlex Crichton $(
104ba4e22bcSAlex Crichton $option::$opt(val) => {
105ba4e22bcSAlex Crichton write!(f, "{}=", stringify!($opt).replace('_', "-"))?;
106ba4e22bcSAlex Crichton $crate::opt::WasmtimeOptionValue::display(val, f)
107ba4e22bcSAlex Crichton }
108ba4e22bcSAlex Crichton )+
109ba4e22bcSAlex Crichton $(
110ba4e22bcSAlex Crichton $option::$prefixed(key, val) => {
111ba4e22bcSAlex Crichton write!(f, "{}-{key}", stringify!($prefixed))?;
112ba4e22bcSAlex Crichton if let Some(val) = val {
113ba4e22bcSAlex Crichton write!(f, "={val}")?;
114ba4e22bcSAlex Crichton }
115ba4e22bcSAlex Crichton Ok(())
116ba4e22bcSAlex Crichton }
117ba4e22bcSAlex Crichton )?
118ba4e22bcSAlex Crichton }
119ba4e22bcSAlex Crichton }
120ba4e22bcSAlex Crichton }
121ba4e22bcSAlex Crichton
1228995750aSAlex Crichton impl $opts {
1238995750aSAlex Crichton fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) {
1248995750aSAlex Crichton for opt in opts.iter().flat_map(|o| o.0.iter()) {
1258995750aSAlex Crichton match opt {
1268995750aSAlex Crichton $(
1278995750aSAlex Crichton $option::$opt(val) => {
128ba4e22bcSAlex Crichton $crate::opt::OptionContainer::push(&mut self.$opt, val.clone());
1298995750aSAlex Crichton }
1308995750aSAlex Crichton )+
1318995750aSAlex Crichton $(
1328995750aSAlex Crichton $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())),
1338995750aSAlex Crichton )?
1348995750aSAlex Crichton }
1358995750aSAlex Crichton }
1368995750aSAlex Crichton }
137ba4e22bcSAlex Crichton
138ba4e22bcSAlex Crichton fn to_options(&self) -> Vec<$option> {
139ba4e22bcSAlex Crichton let mut ret = Vec::new();
140ba4e22bcSAlex Crichton $(
141ba4e22bcSAlex Crichton for item in $crate::opt::OptionContainer::get(&self.$opt) {
142ba4e22bcSAlex Crichton ret.push($option::$opt(item.clone()));
143ba4e22bcSAlex Crichton }
144ba4e22bcSAlex Crichton )+
145ba4e22bcSAlex Crichton $(
146ba4e22bcSAlex Crichton for (key,val) in self.$prefixed.iter() {
147ba4e22bcSAlex Crichton ret.push($option::$prefixed(key.clone(), val.clone()));
148ba4e22bcSAlex Crichton }
149ba4e22bcSAlex Crichton )?
150ba4e22bcSAlex Crichton ret
151ba4e22bcSAlex Crichton }
1528995750aSAlex Crichton }
1538995750aSAlex Crichton };
1548995750aSAlex Crichton }
1558995750aSAlex Crichton
1568995750aSAlex Crichton /// Parser registered with clap which handles parsing the `...` in `-O ...`.
157519808fcSAlex Crichton #[derive(Clone, Debug, PartialEq)]
1588995750aSAlex Crichton pub struct CommaSeparated<T>(pub Vec<T>);
1598995750aSAlex Crichton
1608995750aSAlex Crichton impl<T> ValueParserFactory for CommaSeparated<T>
1618995750aSAlex Crichton where
1628995750aSAlex Crichton T: WasmtimeOption,
1638995750aSAlex Crichton {
1648995750aSAlex Crichton type Parser = CommaSeparatedParser<T>;
1658995750aSAlex Crichton
value_parser() -> CommaSeparatedParser<T>1668995750aSAlex Crichton fn value_parser() -> CommaSeparatedParser<T> {
1678995750aSAlex Crichton CommaSeparatedParser(marker::PhantomData)
1688995750aSAlex Crichton }
1698995750aSAlex Crichton }
1708995750aSAlex Crichton
1718995750aSAlex Crichton #[derive(Clone)]
1728995750aSAlex Crichton pub struct CommaSeparatedParser<T>(marker::PhantomData<T>);
1738995750aSAlex Crichton
1748995750aSAlex Crichton impl<T> TypedValueParser for CommaSeparatedParser<T>
1758995750aSAlex Crichton where
1768995750aSAlex Crichton T: WasmtimeOption,
1778995750aSAlex Crichton {
1788995750aSAlex Crichton type Value = CommaSeparated<T>;
1798995750aSAlex Crichton
parse_ref( &self, cmd: &clap::Command, arg: Option<&clap::Arg>, value: &std::ffi::OsStr, ) -> Result<Self::Value, Error>1808995750aSAlex Crichton fn parse_ref(
1818995750aSAlex Crichton &self,
1828995750aSAlex Crichton cmd: &clap::Command,
1838995750aSAlex Crichton arg: Option<&clap::Arg>,
1848995750aSAlex Crichton value: &std::ffi::OsStr,
1858995750aSAlex Crichton ) -> Result<Self::Value, Error> {
1868995750aSAlex Crichton let val = StringValueParser::new().parse_ref(cmd, arg, value)?;
1878995750aSAlex Crichton
1888995750aSAlex Crichton let options = T::OPTIONS;
1898995750aSAlex Crichton let arg = arg.expect("should always have an argument");
1908995750aSAlex Crichton let arg_long = arg.get_long().expect("should have a long name specified");
1918995750aSAlex Crichton let arg_short = arg.get_short().expect("should have a short name specified");
1928995750aSAlex Crichton
1938995750aSAlex Crichton // Handle `-O help` which dumps all the `-O` options, their messages,
1948995750aSAlex Crichton // and then exits.
1958995750aSAlex Crichton if val == "help" {
1968995750aSAlex Crichton let mut max = 0;
1978995750aSAlex Crichton for d in options {
1988995750aSAlex Crichton max = max.max(d.name.display_string().len() + d.val_help.len());
1998995750aSAlex Crichton }
2008995750aSAlex Crichton println!("Available {arg_long} options:\n");
2018995750aSAlex Crichton for d in options {
2028995750aSAlex Crichton print!(
2038995750aSAlex Crichton " -{arg_short} {:>1$}",
2048995750aSAlex Crichton d.name.display_string(),
2058995750aSAlex Crichton max - d.val_help.len()
2068995750aSAlex Crichton );
2078995750aSAlex Crichton print!("{}", d.val_help);
2088995750aSAlex Crichton print!(" --");
2098995750aSAlex Crichton if val == "help" {
2108995750aSAlex Crichton for line in d.docs.lines().map(|s| s.trim()) {
2118995750aSAlex Crichton if line.is_empty() {
2128995750aSAlex Crichton break;
2138995750aSAlex Crichton }
2148995750aSAlex Crichton print!(" {line}");
2158995750aSAlex Crichton }
2168995750aSAlex Crichton println!();
2178995750aSAlex Crichton } else {
2188995750aSAlex Crichton println!();
2198995750aSAlex Crichton for line in d.docs.lines().map(|s| s.trim()) {
2208995750aSAlex Crichton let line = line.trim();
2218995750aSAlex Crichton println!(" {line}");
2228995750aSAlex Crichton }
2238995750aSAlex Crichton }
2248995750aSAlex Crichton }
2258995750aSAlex Crichton println!("\npass `-{arg_short} help-long` to see longer-form explanations");
2268995750aSAlex Crichton std::process::exit(0);
2278995750aSAlex Crichton }
2288995750aSAlex Crichton if val == "help-long" {
2298995750aSAlex Crichton println!("Available {arg_long} options:\n");
2308995750aSAlex Crichton for d in options {
2318995750aSAlex Crichton println!(
2328995750aSAlex Crichton " -{arg_short} {}{} --",
2338995750aSAlex Crichton d.name.display_string(),
2348995750aSAlex Crichton d.val_help
2358995750aSAlex Crichton );
2368995750aSAlex Crichton println!();
2378995750aSAlex Crichton for line in d.docs.lines().map(|s| s.trim()) {
2388995750aSAlex Crichton let line = line.trim();
2398995750aSAlex Crichton println!(" {line}");
2408995750aSAlex Crichton }
2418995750aSAlex Crichton }
2428995750aSAlex Crichton std::process::exit(0);
2438995750aSAlex Crichton }
2448995750aSAlex Crichton
2458995750aSAlex Crichton let mut result = Vec::new();
2468995750aSAlex Crichton for val in val.split(',') {
2478995750aSAlex Crichton // Split `k=v` into `k` and `v` where `v` is optional
2488995750aSAlex Crichton let mut iter = val.splitn(2, '=');
2498995750aSAlex Crichton let key = iter.next().unwrap();
2508995750aSAlex Crichton let key_val = iter.next();
2518995750aSAlex Crichton
2528995750aSAlex Crichton // Find `key` within `T::OPTIONS`
2538995750aSAlex Crichton let option = options
2548995750aSAlex Crichton .iter()
2558995750aSAlex Crichton .filter_map(|d| match d.name {
2568995750aSAlex Crichton OptName::Name(s) => {
2578995750aSAlex Crichton let s = s.replace('_', "-");
25890ac295eSAlex Crichton if s == key { Some((d, s)) } else { None }
2598995750aSAlex Crichton }
2608995750aSAlex Crichton OptName::Prefix(s) => {
2618995750aSAlex Crichton let name = key.strip_prefix(s)?.strip_prefix("-")?;
2628995750aSAlex Crichton Some((d, name.to_string()))
2638995750aSAlex Crichton }
2648995750aSAlex Crichton })
2658995750aSAlex Crichton .next();
2668995750aSAlex Crichton
2678995750aSAlex Crichton let (desc, key) = match option {
2688995750aSAlex Crichton Some(pair) => pair,
2698995750aSAlex Crichton None => {
2708995750aSAlex Crichton let err = Error::raw(
2718995750aSAlex Crichton ErrorKind::InvalidValue,
2728995750aSAlex Crichton format!("unknown -{arg_short} / --{arg_long} option: {key}\n"),
2738995750aSAlex Crichton );
2748995750aSAlex Crichton return Err(err.with_cmd(cmd));
2758995750aSAlex Crichton }
2768995750aSAlex Crichton };
2778995750aSAlex Crichton
2788995750aSAlex Crichton result.push((desc.parse)(&key, key_val).map_err(|e| {
2798995750aSAlex Crichton Error::raw(
2808995750aSAlex Crichton ErrorKind::InvalidValue,
2818995750aSAlex Crichton format!("failed to parse -{arg_short} option `{val}`: {e:?}\n"),
2828995750aSAlex Crichton )
2838995750aSAlex Crichton .with_cmd(cmd)
2848995750aSAlex Crichton })?)
2858995750aSAlex Crichton }
2868995750aSAlex Crichton
2878995750aSAlex Crichton Ok(CommaSeparated(result))
2888995750aSAlex Crichton }
2898995750aSAlex Crichton }
2908995750aSAlex Crichton
2918995750aSAlex Crichton /// Helper trait used by `CommaSeparated` which contains a list of all options
2928995750aSAlex Crichton /// supported by the option group.
2938995750aSAlex Crichton pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static {
2948995750aSAlex Crichton const OPTIONS: &'static [OptionDesc<Self>];
2958995750aSAlex Crichton }
2968995750aSAlex Crichton
2978995750aSAlex Crichton pub struct OptionDesc<T> {
2988995750aSAlex Crichton pub name: OptName,
2998995750aSAlex Crichton pub docs: &'static str,
3008995750aSAlex Crichton pub parse: fn(&str, Option<&str>) -> Result<T>,
3018995750aSAlex Crichton pub val_help: &'static str,
3028995750aSAlex Crichton }
3038995750aSAlex Crichton
3048995750aSAlex Crichton pub enum OptName {
3058995750aSAlex Crichton /// A named option. Note that the `str` here uses `_` instead of `-` because
3068995750aSAlex Crichton /// it's derived from Rust syntax.
3078995750aSAlex Crichton Name(&'static str),
3088995750aSAlex Crichton
3098995750aSAlex Crichton /// A prefixed option which strips the specified `name`, then `-`.
3108995750aSAlex Crichton Prefix(&'static str),
3118995750aSAlex Crichton }
3128995750aSAlex Crichton
3138995750aSAlex Crichton impl OptName {
display_string(&self) -> String3148995750aSAlex Crichton fn display_string(&self) -> String {
3158995750aSAlex Crichton match self {
3168995750aSAlex Crichton OptName::Name(s) => s.replace('_', "-"),
3178995750aSAlex Crichton OptName::Prefix(s) => format!("{s}-<KEY>"),
3188995750aSAlex Crichton }
3198995750aSAlex Crichton }
3208995750aSAlex Crichton }
3218995750aSAlex Crichton
3228995750aSAlex Crichton /// A helper trait for all types of options that can be parsed. This is what
3238995750aSAlex Crichton /// actually parses the `=val` in `key=val`
3248995750aSAlex Crichton pub trait WasmtimeOptionValue: Sized {
3258995750aSAlex Crichton /// Help text for the value to be specified.
3268995750aSAlex Crichton const VAL_HELP: &'static str;
3278995750aSAlex Crichton
3288995750aSAlex Crichton /// Parses the provided value, if given, returning an error on failure.
parse(val: Option<&str>) -> Result<Self>3298995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self>;
330ba4e22bcSAlex Crichton
331ba4e22bcSAlex Crichton /// Write the value to `f` that would parse to `self`.
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result332ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
3338995750aSAlex Crichton }
3348995750aSAlex Crichton
3358995750aSAlex Crichton impl WasmtimeOptionValue for String {
3368995750aSAlex Crichton const VAL_HELP: &'static str = "=val";
parse(val: Option<&str>) -> Result<Self>3378995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
3388995750aSAlex Crichton match val {
3398995750aSAlex Crichton Some(val) => Ok(val.to_string()),
3408995750aSAlex Crichton None => bail!("value must be specified with `key=val` syntax"),
3418995750aSAlex Crichton }
3428995750aSAlex Crichton }
343ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result344ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345ba4e22bcSAlex Crichton f.write_str(self)
346ba4e22bcSAlex Crichton }
3478995750aSAlex Crichton }
3488995750aSAlex Crichton
349*133a0ef4SChris Fallin impl WasmtimeOptionValue for PathBuf {
350*133a0ef4SChris Fallin const VAL_HELP: &'static str = "=path";
parse(val: Option<&str>) -> Result<Self>351*133a0ef4SChris Fallin fn parse(val: Option<&str>) -> Result<Self> {
352*133a0ef4SChris Fallin match val {
353*133a0ef4SChris Fallin Some(val) => Ok(PathBuf::from_str(val)?),
354*133a0ef4SChris Fallin None => bail!("value must be specified with key=val syntax"),
355*133a0ef4SChris Fallin }
356*133a0ef4SChris Fallin }
357*133a0ef4SChris Fallin
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result358*133a0ef4SChris Fallin fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359*133a0ef4SChris Fallin write!(f, "{self:?}")
360*133a0ef4SChris Fallin }
361*133a0ef4SChris Fallin }
362*133a0ef4SChris Fallin
3638995750aSAlex Crichton impl WasmtimeOptionValue for u32 {
3648995750aSAlex Crichton const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3658995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
36681f9efc0SVictor Adossi let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3678c8725e5SAlex Crichton match val.strip_prefix("0x") {
3688c8725e5SAlex Crichton Some(hex) => Ok(u32::from_str_radix(hex, 16)?),
3698c8725e5SAlex Crichton None => Ok(val.parse()?),
3708c8725e5SAlex Crichton }
3718995750aSAlex Crichton }
372ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result373ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
374ba4e22bcSAlex Crichton write!(f, "{self}")
375ba4e22bcSAlex Crichton }
3768995750aSAlex Crichton }
3778995750aSAlex Crichton
3788995750aSAlex Crichton impl WasmtimeOptionValue for u64 {
3798995750aSAlex Crichton const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3808995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
38181f9efc0SVictor Adossi let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3828c8725e5SAlex Crichton match val.strip_prefix("0x") {
3838c8725e5SAlex Crichton Some(hex) => Ok(u64::from_str_radix(hex, 16)?),
3848c8725e5SAlex Crichton None => Ok(val.parse()?),
3858c8725e5SAlex Crichton }
3868995750aSAlex Crichton }
387ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result388ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389ba4e22bcSAlex Crichton write!(f, "{self}")
390ba4e22bcSAlex Crichton }
3918995750aSAlex Crichton }
3928995750aSAlex Crichton
3938995750aSAlex Crichton impl WasmtimeOptionValue for usize {
3948995750aSAlex Crichton const VAL_HELP: &'static str = "=N";
parse(val: Option<&str>) -> Result<Self>3958995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
39681f9efc0SVictor Adossi let val = String::parse(val)?.replace(IGNORED_NUMBER_CHARS, "");
3978c8725e5SAlex Crichton match val.strip_prefix("0x") {
3988c8725e5SAlex Crichton Some(hex) => Ok(usize::from_str_radix(hex, 16)?),
3998c8725e5SAlex Crichton None => Ok(val.parse()?),
4008c8725e5SAlex Crichton }
4018995750aSAlex Crichton }
402ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result403ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
404ba4e22bcSAlex Crichton write!(f, "{self}")
405ba4e22bcSAlex Crichton }
4068995750aSAlex Crichton }
4078995750aSAlex Crichton
4088995750aSAlex Crichton impl WasmtimeOptionValue for bool {
4098995750aSAlex Crichton const VAL_HELP: &'static str = "[=y|n]";
parse(val: Option<&str>) -> Result<Self>4108995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
4118995750aSAlex Crichton match val {
4128995750aSAlex Crichton None | Some("y") | Some("yes") | Some("true") => Ok(true),
4138995750aSAlex Crichton Some("n") | Some("no") | Some("false") => Ok(false),
4148995750aSAlex Crichton Some(s) => bail!("unknown boolean flag `{s}`, only yes,no,<nothing> accepted"),
4158995750aSAlex Crichton }
4168995750aSAlex Crichton }
417ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result418ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419ba4e22bcSAlex Crichton if *self {
420ba4e22bcSAlex Crichton f.write_str("y")
421ba4e22bcSAlex Crichton } else {
422ba4e22bcSAlex Crichton f.write_str("n")
423ba4e22bcSAlex Crichton }
424ba4e22bcSAlex Crichton }
4258995750aSAlex Crichton }
4268995750aSAlex Crichton
4278995750aSAlex Crichton impl WasmtimeOptionValue for Duration {
4288995750aSAlex Crichton const VAL_HELP: &'static str = "=N|Ns|Nms|..";
parse(val: Option<&str>) -> Result<Duration>4298995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Duration> {
4308995750aSAlex Crichton let s = String::parse(val)?;
4318995750aSAlex Crichton // assume an integer without a unit specified is a number of seconds ...
4328995750aSAlex Crichton if let Ok(val) = s.parse() {
4338995750aSAlex Crichton return Ok(Duration::from_secs(val));
4348995750aSAlex Crichton }
435bda83128SAlex Crichton
436bda83128SAlex Crichton if let Some(num) = s.strip_suffix("s") {
437bda83128SAlex Crichton if let Ok(val) = num.parse() {
438bda83128SAlex Crichton return Ok(Duration::from_secs(val));
439bda83128SAlex Crichton }
440bda83128SAlex Crichton }
441bda83128SAlex Crichton if let Some(num) = s.strip_suffix("ms") {
442bda83128SAlex Crichton if let Ok(val) = num.parse() {
443bda83128SAlex Crichton return Ok(Duration::from_millis(val));
444bda83128SAlex Crichton }
445bda83128SAlex Crichton }
446bda83128SAlex Crichton if let Some(num) = s.strip_suffix("us").or(s.strip_suffix("μs")) {
447bda83128SAlex Crichton if let Ok(val) = num.parse() {
448bda83128SAlex Crichton return Ok(Duration::from_micros(val));
449bda83128SAlex Crichton }
450bda83128SAlex Crichton }
451bda83128SAlex Crichton if let Some(num) = s.strip_suffix("ns") {
452bda83128SAlex Crichton if let Ok(val) = num.parse() {
453bda83128SAlex Crichton return Ok(Duration::from_nanos(val));
454bda83128SAlex Crichton }
455bda83128SAlex Crichton }
456bda83128SAlex Crichton
457bda83128SAlex Crichton bail!("failed to parse duration: {s}")
4588995750aSAlex Crichton }
459ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result460ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
461bda83128SAlex Crichton let subsec = self.subsec_nanos();
462bda83128SAlex Crichton if subsec == 0 {
463bda83128SAlex Crichton write!(f, "{}s", self.as_secs())
464bda83128SAlex Crichton } else if subsec % 1_000 == 0 {
465bda83128SAlex Crichton write!(f, "{}μs", self.as_micros())
466bda83128SAlex Crichton } else if subsec % 1_000_000 == 0 {
467bda83128SAlex Crichton write!(f, "{}ms", self.as_millis())
468bda83128SAlex Crichton } else {
469bda83128SAlex Crichton write!(f, "{}ns", self.as_nanos())
470bda83128SAlex Crichton }
471ba4e22bcSAlex Crichton }
4728995750aSAlex Crichton }
4738995750aSAlex Crichton
4748995750aSAlex Crichton impl WasmtimeOptionValue for wasmtime::OptLevel {
4758995750aSAlex Crichton const VAL_HELP: &'static str = "=0|1|2|s";
parse(val: Option<&str>) -> Result<Self>4768995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
4778995750aSAlex Crichton match String::parse(val)?.as_str() {
4788995750aSAlex Crichton "0" => Ok(wasmtime::OptLevel::None),
4798995750aSAlex Crichton "1" => Ok(wasmtime::OptLevel::Speed),
4808995750aSAlex Crichton "2" => Ok(wasmtime::OptLevel::Speed),
4818995750aSAlex Crichton "s" => Ok(wasmtime::OptLevel::SpeedAndSize),
482557cc2d6SAlex Crichton other => bail!("unknown optimization level `{other}`, only 0,1,2,s accepted"),
4838995750aSAlex Crichton }
4848995750aSAlex Crichton }
485ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result486ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487ba4e22bcSAlex Crichton match *self {
488ba4e22bcSAlex Crichton wasmtime::OptLevel::None => f.write_str("0"),
489ba4e22bcSAlex Crichton wasmtime::OptLevel::Speed => f.write_str("2"),
490ba4e22bcSAlex Crichton wasmtime::OptLevel::SpeedAndSize => f.write_str("s"),
491ba4e22bcSAlex Crichton _ => unreachable!(),
492ba4e22bcSAlex Crichton }
493ba4e22bcSAlex Crichton }
4948995750aSAlex Crichton }
4958995750aSAlex Crichton
4961e3e5fc1SChris Fallin impl WasmtimeOptionValue for wasmtime::RegallocAlgorithm {
4971e3e5fc1SChris Fallin const VAL_HELP: &'static str = "=backtracking|single-pass";
parse(val: Option<&str>) -> Result<Self>4981e3e5fc1SChris Fallin fn parse(val: Option<&str>) -> Result<Self> {
4991e3e5fc1SChris Fallin match String::parse(val)?.as_str() {
5001e3e5fc1SChris Fallin "backtracking" => Ok(wasmtime::RegallocAlgorithm::Backtracking),
50173de2ee9SChris Fallin "single-pass" => Ok(wasmtime::RegallocAlgorithm::SinglePass),
502557cc2d6SAlex Crichton other => {
503557cc2d6SAlex Crichton bail!("unknown regalloc algorithm`{other}`, only backtracking,single-pass accepted")
504557cc2d6SAlex Crichton }
5051e3e5fc1SChris Fallin }
5061e3e5fc1SChris Fallin }
507ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result508ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509ba4e22bcSAlex Crichton match *self {
510ba4e22bcSAlex Crichton wasmtime::RegallocAlgorithm::Backtracking => f.write_str("backtracking"),
51173de2ee9SChris Fallin wasmtime::RegallocAlgorithm::SinglePass => f.write_str("single-pass"),
512ba4e22bcSAlex Crichton _ => unreachable!(),
513ba4e22bcSAlex Crichton }
514ba4e22bcSAlex Crichton }
5151e3e5fc1SChris Fallin }
5161e3e5fc1SChris Fallin
5178995750aSAlex Crichton impl WasmtimeOptionValue for wasmtime::Strategy {
5188995750aSAlex Crichton const VAL_HELP: &'static str = "=winch|cranelift";
parse(val: Option<&str>) -> Result<Self>5198995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
5208995750aSAlex Crichton match String::parse(val)?.as_str() {
5218995750aSAlex Crichton "cranelift" => Ok(wasmtime::Strategy::Cranelift),
5228995750aSAlex Crichton "winch" => Ok(wasmtime::Strategy::Winch),
523519808fcSAlex Crichton other => bail!("unknown compiler `{other}` only `cranelift` and `winch` accepted",),
5248995750aSAlex Crichton }
5258995750aSAlex Crichton }
526ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result527ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528ba4e22bcSAlex Crichton match *self {
529ba4e22bcSAlex Crichton wasmtime::Strategy::Cranelift => f.write_str("cranelift"),
530ba4e22bcSAlex Crichton wasmtime::Strategy::Winch => f.write_str("winch"),
531ba4e22bcSAlex Crichton _ => unreachable!(),
532ba4e22bcSAlex Crichton }
533ba4e22bcSAlex Crichton }
5348995750aSAlex Crichton }
5358995750aSAlex Crichton
53683bf774dSNick Fitzgerald impl WasmtimeOptionValue for wasmtime::Collector {
53783bf774dSNick Fitzgerald const VAL_HELP: &'static str = "=drc|null";
parse(val: Option<&str>) -> Result<Self>53883bf774dSNick Fitzgerald fn parse(val: Option<&str>) -> Result<Self> {
53983bf774dSNick Fitzgerald match String::parse(val)?.as_str() {
54083bf774dSNick Fitzgerald "drc" => Ok(wasmtime::Collector::DeferredReferenceCounting),
54183bf774dSNick Fitzgerald "null" => Ok(wasmtime::Collector::Null),
54283bf774dSNick Fitzgerald other => bail!("unknown collector `{other}` only `drc` and `null` accepted",),
54383bf774dSNick Fitzgerald }
54483bf774dSNick Fitzgerald }
545ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result546ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547ba4e22bcSAlex Crichton match *self {
548ba4e22bcSAlex Crichton wasmtime::Collector::DeferredReferenceCounting => f.write_str("drc"),
549ba4e22bcSAlex Crichton wasmtime::Collector::Null => f.write_str("null"),
550ba4e22bcSAlex Crichton _ => unreachable!(),
551ba4e22bcSAlex Crichton }
552ba4e22bcSAlex Crichton }
553ba4e22bcSAlex Crichton }
554ba4e22bcSAlex Crichton
555698028ceSAlex Crichton impl WasmtimeOptionValue for wasmtime::Enabled {
556ba4e22bcSAlex Crichton const VAL_HELP: &'static str = "[=y|n|auto]";
parse(val: Option<&str>) -> Result<Self>557ba4e22bcSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
558ba4e22bcSAlex Crichton match val {
559698028ceSAlex Crichton None | Some("y") | Some("yes") | Some("true") => Ok(wasmtime::Enabled::Yes),
560698028ceSAlex Crichton Some("n") | Some("no") | Some("false") => Ok(wasmtime::Enabled::No),
561698028ceSAlex Crichton Some("auto") => Ok(wasmtime::Enabled::Auto),
562698028ceSAlex Crichton Some(s) => bail!("unknown flag `{s}`, only yes,no,auto,<nothing> accepted"),
563ba4e22bcSAlex Crichton }
564ba4e22bcSAlex Crichton }
565ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result566ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
567ba4e22bcSAlex Crichton match *self {
568698028ceSAlex Crichton wasmtime::Enabled::Yes => f.write_str("y"),
569698028ceSAlex Crichton wasmtime::Enabled::No => f.write_str("n"),
570698028ceSAlex Crichton wasmtime::Enabled::Auto => f.write_str("auto"),
571ba4e22bcSAlex Crichton }
572ba4e22bcSAlex Crichton }
57383bf774dSNick Fitzgerald }
57483bf774dSNick Fitzgerald
5758995750aSAlex Crichton impl WasmtimeOptionValue for WasiNnGraph {
5768995750aSAlex Crichton const VAL_HELP: &'static str = "=<format>::<dir>";
parse(val: Option<&str>) -> Result<Self>5778995750aSAlex Crichton fn parse(val: Option<&str>) -> Result<Self> {
5788995750aSAlex Crichton let val = String::parse(val)?;
5798995750aSAlex Crichton let mut parts = val.splitn(2, "::");
5808995750aSAlex Crichton Ok(WasiNnGraph {
5818995750aSAlex Crichton format: parts.next().unwrap().to_string(),
5828995750aSAlex Crichton dir: match parts.next() {
5838995750aSAlex Crichton Some(part) => part.into(),
5848995750aSAlex Crichton None => bail!("graph does not contain `::` separator for directory"),
5858995750aSAlex Crichton },
5868995750aSAlex Crichton })
5878995750aSAlex Crichton }
588ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result589ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590ba4e22bcSAlex Crichton write!(f, "{}::{}", self.format, self.dir)
591ba4e22bcSAlex Crichton }
5928995750aSAlex Crichton }
5934b842a05SXinzhao Xu
59412fc764dSXinzhao Xu impl WasmtimeOptionValue for KeyValuePair {
5954b842a05SXinzhao Xu const VAL_HELP: &'static str = "=<name>=<val>";
parse(val: Option<&str>) -> Result<Self>5964b842a05SXinzhao Xu fn parse(val: Option<&str>) -> Result<Self> {
5974b842a05SXinzhao Xu let val = String::parse(val)?;
5984b842a05SXinzhao Xu let mut parts = val.splitn(2, "=");
59912fc764dSXinzhao Xu Ok(KeyValuePair {
6004b842a05SXinzhao Xu key: parts.next().unwrap().to_string(),
6014b842a05SXinzhao Xu value: match parts.next() {
6024b842a05SXinzhao Xu Some(part) => part.into(),
6034b842a05SXinzhao Xu None => "".to_string(),
6044b842a05SXinzhao Xu },
6054b842a05SXinzhao Xu })
6064b842a05SXinzhao Xu }
607ba4e22bcSAlex Crichton
display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result608ba4e22bcSAlex Crichton fn display(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
609ba4e22bcSAlex Crichton f.write_str(&self.key)?;
610ba4e22bcSAlex Crichton if !self.value.is_empty() {
611ba4e22bcSAlex Crichton f.write_str("=")?;
612ba4e22bcSAlex Crichton f.write_str(&self.value)?;
613ba4e22bcSAlex Crichton }
614ba4e22bcSAlex Crichton Ok(())
615ba4e22bcSAlex Crichton }
616ba4e22bcSAlex Crichton }
617ba4e22bcSAlex Crichton
618ba4e22bcSAlex Crichton pub trait OptionContainer<T> {
push(&mut self, val: T)619ba4e22bcSAlex Crichton fn push(&mut self, val: T);
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a620ba4e22bcSAlex Crichton fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
621ba4e22bcSAlex Crichton where
622ba4e22bcSAlex Crichton T: 'a;
623ba4e22bcSAlex Crichton }
624ba4e22bcSAlex Crichton
625ba4e22bcSAlex Crichton impl<T> OptionContainer<T> for Option<T> {
push(&mut self, val: T)626ba4e22bcSAlex Crichton fn push(&mut self, val: T) {
627ba4e22bcSAlex Crichton *self = Some(val);
628ba4e22bcSAlex Crichton }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,629ba4e22bcSAlex Crichton fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
630ba4e22bcSAlex Crichton where
631ba4e22bcSAlex Crichton T: 'a,
632ba4e22bcSAlex Crichton {
633ba4e22bcSAlex Crichton self.iter()
634ba4e22bcSAlex Crichton }
635ba4e22bcSAlex Crichton }
636ba4e22bcSAlex Crichton
637ba4e22bcSAlex Crichton impl<T> OptionContainer<T> for Vec<T> {
push(&mut self, val: T)638ba4e22bcSAlex Crichton fn push(&mut self, val: T) {
639ba4e22bcSAlex Crichton Vec::push(self, val);
640ba4e22bcSAlex Crichton }
get<'a>(&'a self) -> impl Iterator<Item = &'a T> where T: 'a,641ba4e22bcSAlex Crichton fn get<'a>(&'a self) -> impl Iterator<Item = &'a T>
642ba4e22bcSAlex Crichton where
643ba4e22bcSAlex Crichton T: 'a,
644ba4e22bcSAlex Crichton {
645ba4e22bcSAlex Crichton self.iter()
646ba4e22bcSAlex Crichton }
6474b842a05SXinzhao Xu }
64881f9efc0SVictor Adossi
6492a5c141cSLudvig Liljenberg // Used to parse toml values into string so that we can reuse the `WasmtimeOptionValue::parse`
6502a5c141cSLudvig Liljenberg // for parsing toml values the same way we parse command line values.
6512a5c141cSLudvig Liljenberg //
6522a5c141cSLudvig Liljenberg // Used for wasmtime::Strategy, wasmtime::Collector, wasmtime::OptLevel, wasmtime::RegallocAlgorithm
6532a5c141cSLudvig Liljenberg struct ToStringVisitor {}
6542a5c141cSLudvig Liljenberg
6552a5c141cSLudvig Liljenberg impl<'de> Visitor<'de> for ToStringVisitor {
6562a5c141cSLudvig Liljenberg type Value = String;
6572a5c141cSLudvig Liljenberg
expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result6582a5c141cSLudvig Liljenberg fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
6592a5c141cSLudvig Liljenberg write!(formatter, "&str, u64, or i64")
6602a5c141cSLudvig Liljenberg }
6612a5c141cSLudvig Liljenberg
visit_str<E>(self, s: &str) -> Result<Self::Value, E> where E: de::Error,6622a5c141cSLudvig Liljenberg fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
6632a5c141cSLudvig Liljenberg where
6642a5c141cSLudvig Liljenberg E: de::Error,
6652a5c141cSLudvig Liljenberg {
6662a5c141cSLudvig Liljenberg Ok(s.to_owned())
6672a5c141cSLudvig Liljenberg }
6682a5c141cSLudvig Liljenberg
visit_u64<E>(self, v: u64) -> Result<Self::Value, E> where E: de::Error,6692a5c141cSLudvig Liljenberg fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
6702a5c141cSLudvig Liljenberg where
6712a5c141cSLudvig Liljenberg E: de::Error,
6722a5c141cSLudvig Liljenberg {
6732a5c141cSLudvig Liljenberg Ok(v.to_string())
6742a5c141cSLudvig Liljenberg }
6752a5c141cSLudvig Liljenberg
visit_i64<E>(self, v: i64) -> Result<Self::Value, E> where E: de::Error,6762a5c141cSLudvig Liljenberg fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
6772a5c141cSLudvig Liljenberg where
6782a5c141cSLudvig Liljenberg E: de::Error,
6792a5c141cSLudvig Liljenberg {
6802a5c141cSLudvig Liljenberg Ok(v.to_string())
6812a5c141cSLudvig Liljenberg }
6822a5c141cSLudvig Liljenberg }
6832a5c141cSLudvig Liljenberg
6842a5c141cSLudvig Liljenberg // Deserializer that uses the `WasmtimeOptionValue::parse` to parse toml values
cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error> where T: WasmtimeOptionValue, D: serde::Deserializer<'de>,6852a5c141cSLudvig Liljenberg pub(crate) fn cli_parse_wrapper<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
6862a5c141cSLudvig Liljenberg where
6872a5c141cSLudvig Liljenberg T: WasmtimeOptionValue,
6882a5c141cSLudvig Liljenberg D: serde::Deserializer<'de>,
6892a5c141cSLudvig Liljenberg {
6902a5c141cSLudvig Liljenberg let to_string_visitor = ToStringVisitor {};
6912a5c141cSLudvig Liljenberg let str = deserializer.deserialize_any(to_string_visitor)?;
6922a5c141cSLudvig Liljenberg
6932a5c141cSLudvig Liljenberg T::parse(Some(&str))
6942a5c141cSLudvig Liljenberg .map(Some)
6952a5c141cSLudvig Liljenberg .map_err(serde::de::Error::custom)
6962a5c141cSLudvig Liljenberg }
6972a5c141cSLudvig Liljenberg
69881f9efc0SVictor Adossi #[cfg(test)]
69981f9efc0SVictor Adossi mod tests {
70081f9efc0SVictor Adossi use super::WasmtimeOptionValue;
70181f9efc0SVictor Adossi
70281f9efc0SVictor Adossi #[test]
numbers_with_underscores()70381f9efc0SVictor Adossi fn numbers_with_underscores() {
70481f9efc0SVictor Adossi assert!(<u32 as WasmtimeOptionValue>::parse(Some("123")).is_ok_and(|v| v == 123));
70581f9efc0SVictor Adossi assert!(<u32 as WasmtimeOptionValue>::parse(Some("1_2_3")).is_ok_and(|v| v == 123));
70681f9efc0SVictor Adossi }
70781f9efc0SVictor Adossi }
708