use std::ffi::{OsStr, OsString};
use std::marker::PhantomData;
use std::num::NonZero;
use std::ops::ControlFlow;
use os_str_bytes::OsStrBytesExt;
use ref_cast::RefCast;
use crate::Result;
use crate::error::ErrorKind;
use crate::refl::{RawArgsInfo, RawSubcommandInfo};
use crate::shared::ArgAttrs;
use crate::values::ValueParser;
use super::Error;
#[doc(hidden)]
pub trait ParserInternal: Sized {
fn __parse_toplevel(p: &mut RawParser, program_name: &OsStr) -> Result<Self>;
}
#[diagnostic::on_unimplemented(
message = "cannot flatten `{Self}` which is not a `palc::Args`",
label = "this type is expected to have `derive(palc::Args)` but it is not"
)]
#[doc(hidden)]
pub trait Args: Sized + 'static {
#[doc(hidden)]
type __State: ParserState<Output = Self>;
}
pub struct FallbackState<T>(PhantomData<T>);
impl<T> Default for FallbackState<T> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<T: 'static> ParserState for FallbackState<T> {
type Output = T;
const RAW_ARGS_INFO: &'static RawArgsInfo = RawArgsInfo::EMPTY_REF;
const TOTAL_ARG_CNT: u8 = 0;
const TOTAL_UNNAMED_ARG_CNT: u8 = 0;
fn finish(&mut self) -> Result<Self::Output> {
unreachable!()
}
}
impl<T: 'static> ParserStateDyn for FallbackState<T> {}
pub fn parse_default_str<P: ValueParser>(s: &str, _: P) -> Result<P::Output> {
P::parse(s.as_ref())
}
pub fn assert_impl_display_for_help<T: std::fmt::Display>(x: T) -> T {
x
}
pub fn unknown_subcommand<T>(name: &OsStr) -> Result<T> {
Err(ErrorKind::UnknownSubcommand.with_input(name.into()))
}
pub fn missing_required_arg<S: ParserState, T>(idx: u8) -> Result<T> {
Err(ErrorKind::MissingRequiredArgument.with_arg_idx::<S>(idx))
}
pub fn missing_required_subcmd<T>() -> Result<T> {
Err(ErrorKind::MissingRequiredSubcommand.into())
}
pub fn constraint_required<S: ParserState, T>(idx: u8) -> Result<T> {
Err(ErrorKind::ConstraintRequired.with_arg_idx::<S>(idx))
}
pub fn constraint_exclusive<S: ParserState, T>(idx: u8) -> Result<T> {
Err(ErrorKind::ConstraintExclusive.with_arg_idx::<S>(idx))
}
pub fn constraint_conflict<S: ParserState, T>(idx: u8) -> Result<T> {
Err(ErrorKind::ConstraintConflict.with_arg_idx::<S>(idx))
}
pub trait Parsable {
fn parse_from(
&mut self,
p: &mut RawParser,
attrs: ArgAttrs,
value: &OsStr,
cur_cmd_name: &OsStr,
ancestors: &mut dyn ParserChain,
) -> Result<()>;
}
pub trait FieldState: Default {
type Value;
type Output;
fn place<P: ValueParser<Output = Self::Value>>(&mut self, _: P) -> &mut dyn Parsable;
fn finish(&mut self) -> Self::Output;
fn finish_opt(&mut self) -> Option<Self::Output>;
fn is_set(&self) -> bool;
fn set_default(&mut self, f: impl FnOnce() -> Self::Output);
fn set_default_parse<P: ValueParser<Output = Self::Value>>(&mut self, default: &str, p: P) {
#[inline(never)]
fn set_from_default_dyn(p: &mut dyn Parsable, default: &OsStr) {
p.parse_from(
&mut RawParser::new(&mut std::iter::empty()),
ArgAttrs::default(),
default.as_ref(),
"".as_ref(),
&mut (),
)
.expect("invalid default value");
}
if !self.is_set() {
set_from_default_dyn(self.place(p), default.as_ref());
}
}
}
#[derive(Default)]
pub struct FlagPlace(Option<bool>);
impl FieldState for FlagPlace {
type Value = bool;
type Output = bool;
fn place<P: ValueParser<Output = bool>>(&mut self, _: P) -> &mut dyn Parsable {
self
}
fn finish(&mut self) -> bool {
self.0.unwrap_or_default()
}
fn finish_opt(&mut self) -> Option<Self::Output> {
unreachable!()
}
fn is_set(&self) -> bool {
self.0.is_some()
}
fn set_default(&mut self, f: impl FnOnce() -> Self::Value) {
self.0.get_or_insert_with(f);
}
}
impl Parsable for FlagPlace {
fn parse_from(
&mut self,
_: &mut RawParser,
_: ArgAttrs,
_: &OsStr,
_: &OsStr,
_: &mut dyn ParserChain,
) -> Result<()> {
if self.0.is_some() {
return Err(ErrorKind::DuplicatedNamedArgument.into());
}
self.0 = Some(true);
Ok(())
}
}
pub struct VecPlace<T>(Vec<T>);
impl<T> Default for VecPlace<T> {
fn default() -> Self {
Self(Vec::new())
}
}
impl<T> FieldState for VecPlace<T> {
type Value = T;
type Output = Vec<T>;
fn place<P: ValueParser<Output = Self::Value>>(&mut self, _: P) -> &mut dyn Parsable {
<VecParser<P>>::ref_cast_mut(self)
}
fn finish(&mut self) -> Vec<T> {
std::mem::take(&mut self.0)
}
fn finish_opt(&mut self) -> Option<Vec<T>> {
if self.0.is_empty() { None } else { Some(self.finish()) }
}
fn is_set(&self) -> bool {
!self.0.is_empty()
}
fn set_default(&mut self, f: impl FnOnce() -> Self::Output) {
if self.0.is_empty() {
self.0 = f();
}
}
}
#[derive(RefCast)]
#[repr(transparent)]
struct VecParser<P: ValueParser>(VecPlace<P::Output>);
impl<P: ValueParser> Parsable for VecParser<P> {
fn parse_from(
&mut self,
p: &mut RawParser,
attrs: ArgAttrs,
value: &OsStr,
_: &OsStr,
_: &mut dyn ParserChain,
) -> Result<()> {
let v = &mut self.0.0;
let feed: &mut dyn FnMut(&OsStr) -> Result<()> = if let Some(delim) = attrs.get_delimiter()
{
&mut move |value| {
for frag in value.split(char::from(delim.get())) {
v.push(P::parse(frag)?);
}
Ok(())
}
} else {
&mut |value| {
v.push(P::parse(value)?);
Ok(())
}
};
feed(value)?;
if attrs.contains(ArgAttrs::GREEDY) {
for value in &mut p.iter {
feed(&value)?;
}
}
Ok(())
}
}
pub struct SetValuePlace<T>(Option<T>);
impl<T> Default for SetValuePlace<T> {
fn default() -> Self {
Self(None)
}
}
impl<T> FieldState for SetValuePlace<T> {
type Value = T;
type Output = T;
fn place<P: ValueParser<Output = Self::Value>>(&mut self, _: P) -> &mut dyn Parsable {
<SetValueParser<P>>::ref_cast_mut(self)
}
fn finish(&mut self) -> T {
self.0.take().unwrap()
}
fn finish_opt(&mut self) -> Option<T> {
self.0.take()
}
fn is_set(&self) -> bool {
self.0.is_some()
}
fn set_default(&mut self, f: impl FnOnce() -> Self::Value) {
self.0.get_or_insert_with(f);
}
}
#[derive(RefCast)]
#[repr(transparent)]
struct SetValueParser<P: ValueParser>(SetValuePlace<P::Output>);
impl<P: ValueParser> Parsable for SetValueParser<P> {
fn parse_from(
&mut self,
_: &mut RawParser,
_: ArgAttrs,
value: &OsStr,
_: &OsStr,
_: &mut dyn ParserChain,
) -> Result<()> {
if self.0.0.is_some() {
return Err(ErrorKind::DuplicatedNamedArgument.into());
}
self.0.0 = Some(P::parse(value)?);
Ok(())
}
}
pub struct SetOptionalValuePlace<T>(Option<Option<T>>);
impl<T> Default for SetOptionalValuePlace<T> {
fn default() -> Self {
Self(None)
}
}
impl<T> FieldState for SetOptionalValuePlace<T> {
type Value = T;
type Output = Option<T>;
fn place<P: ValueParser<Output = Self::Value>>(&mut self, _: P) -> &mut dyn Parsable {
<SetOptionalValueParser<P>>::ref_cast_mut(self)
}
fn finish(&mut self) -> Option<T> {
unreachable!()
}
fn finish_opt(&mut self) -> Option<Option<T>> {
self.0.take()
}
fn is_set(&self) -> bool {
self.0.is_some()
}
fn set_default(&mut self, f: impl FnOnce() -> Self::Output) {
self.0.get_or_insert_with(f);
}
}
#[derive(RefCast)]
#[repr(transparent)]
struct SetOptionalValueParser<P: ValueParser>(SetOptionalValuePlace<P::Output>);
impl<P: ValueParser> Parsable for SetOptionalValueParser<P> {
fn parse_from(
&mut self,
_: &mut RawParser,
_: ArgAttrs,
value: &OsStr,
_: &OsStr,
_: &mut dyn ParserChain,
) -> Result<()> {
if self.0.0.is_some() {
return Err(ErrorKind::DuplicatedNamedArgument.into());
}
self.0.0 = Some(if value.is_empty() { None } else { Some(P::parse(value)?) });
Ok(())
}
}
pub(crate) struct ParserChainNode<'a, 'b, 'c> {
pub cmd_name: &'a OsStr,
pub state: &'b mut dyn ParserStateDyn,
pub ancestors: &'c mut dyn ParserChain,
}
pub trait ParserChain {
#[expect(private_interfaces, reason = "not used by proc-macro")]
fn out(&mut self) -> Option<ParserChainNode<'_, '_, '_>> {
None
}
}
impl dyn ParserChain + '_ {
fn feed_global_named(
&mut self,
enc_name: &str,
) -> ControlFlow<(&mut dyn Parsable, ArgAttrs, &'static RawArgsInfo)> {
let Some(node) = self.out() else { return ControlFlow::Continue(()) };
let info = node.state.info();
if let ControlFlow::Break((place, attrs)) = node.state.feed_named(enc_name) {
if attrs.contains(ArgAttrs::GLOBAL) {
return ControlFlow::Break((place, attrs, info));
}
}
node.ancestors.feed_global_named(enc_name)
}
}
impl ParserChain for () {}
impl ParserChain for ParserChainNode<'_, '_, '_> {
fn out(&mut self) -> Option<ParserChainNode<'_, '_, '_>> {
let ParserChainNode { cmd_name, state, ancestors } = self;
Some(ParserChainNode { cmd_name, state: &mut **state, ancestors: &mut **ancestors })
}
}
pub fn place_for_subcommand<G: GetSubcommand>(state: &mut G::State) -> FeedUnnamed<'_> {
#[derive(RefCast)]
#[repr(transparent)]
struct Place<G: GetSubcommand>(G::State);
impl<G: GetSubcommand> Parsable for Place<G> {
fn parse_from(
&mut self,
p: &mut RawParser,
_: ArgAttrs,
value: &OsStr,
cur_cmd_name: &OsStr,
ancestors: &mut dyn ParserChain,
) -> Result<()> {
let states =
&mut ParserChainNode { cmd_name: cur_cmd_name, state: &mut self.0, ancestors };
let subcmd = G::Subcommand::try_parse_with_name(p, value, states)?;
*G::get(&mut self.0) = Some(subcmd);
Ok(())
}
}
ControlFlow::Break((Place::<G>::ref_cast_mut(state), ArgAttrs::default()))
}
pub type FeedNamed<'s> = ControlFlow<(&'s mut dyn Parsable, ArgAttrs)>;
pub type FeedUnnamed<'s> = FeedNamed<'s>;
pub trait ParserState: Default + ParserStateDyn {
type Output;
const RAW_ARGS_INFO: &'static RawArgsInfo = RawArgsInfo::EMPTY_REF;
const HAS_SUBCOMMAND: bool = false;
const TOTAL_ARG_CNT: u8 = 0;
const TOTAL_UNNAMED_ARG_CNT: u8 = 0;
fn finish(&mut self) -> Result<Self::Output>;
}
pub trait GetSubcommand: 'static {
type State: ParserState;
type Subcommand: Subcommand;
fn get(state: &mut Self::State) -> &mut Option<Self::Subcommand>;
}
pub trait ParserStateDyn: 'static {
fn feed_named(&mut self, _enc_name: &str) -> FeedNamed<'_> {
ControlFlow::Continue(())
}
fn feed_unnamed(&mut self, _arg: &OsStr, _idx: usize, _is_last: bool) -> FeedUnnamed<'_> {
ControlFlow::Continue(())
}
fn info(&self) -> &'static RawArgsInfo {
RawArgsInfo::EMPTY_REF
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` is not a `palc::Subcommand`",
label = "this type is expected to have `derive(palc::Subcommand)` but it is not"
)]
#[doc(hidden)]
pub trait Subcommand: Sized + 'static {
const RAW_INFO: &'static RawSubcommandInfo = RawSubcommandInfo::EMPTY_REF;
fn feed_subcommand(_name: &OsStr) -> FeedSubcommand<Self> {
None
}
fn try_parse_with_name(
p: &mut RawParser,
name: &OsStr,
states: &mut dyn ParserChain,
) -> Result<Self> {
let Some(f) = Self::feed_subcommand(name) else {
return unknown_subcommand(name);
};
f(p, name, states)
}
}
pub type FeedSubcommand<T> =
Option<fn(p: &mut RawParser, subcmd: &OsStr, states: &mut dyn ParserChain) -> Result<T>>;
#[cfg(test)]
fn _assert_feed_subcommand_ty<S: ParserState>() -> FeedSubcommand<S::Output> {
Some(try_parse_state::<S>)
}
pub fn try_parse_state<S: ParserState>(
p: &mut RawParser,
cmd_name: &OsStr,
ancestors: &mut dyn ParserChain,
) -> Result<S::Output> {
let mut state = S::default();
let node = &mut ParserChainNode { cmd_name, state: &mut state, ancestors };
try_parse_state_dyn(p, node)?;
state.finish()
}
#[inline(never)]
fn try_parse_state_dyn(p: &mut RawParser, chain: &mut ParserChainNode) -> Result<()> {
let mut buf = OsString::new();
let mut unnamed_idx = 0usize;
while let Some(arg) = p.next_arg(&mut buf)? {
match arg {
Arg::EncodedNamed(enc_name, has_eq, value) => {
let args_info = chain.state.info();
let (place, attrs, args_info) = match chain.state.feed_named(enc_name) {
ControlFlow::Break((place, attrs)) => (place, attrs, args_info),
ControlFlow::Continue(()) => {
match chain.ancestors.feed_global_named(enc_name) {
ControlFlow::Break(ret) => ret,
ControlFlow::Continue(()) => {
#[cfg(feature = "help")]
if enc_name == "h" || enc_name == "help" {
return Err(
Error::from(ErrorKind::Help).maybe_render_help(chain)
);
}
let mut dec_name = String::with_capacity(2 + enc_name.len());
if enc_name.chars().nth(1).is_none() {
dec_name.push('-');
} else if !enc_name.starts_with("--") {
dec_name.push_str("--");
}
dec_name.push_str(enc_name);
return Err(
ErrorKind::UnknownNamedArgument.with_input(dec_name.into())
);
}
}
}
};
if attrs.contains(ArgAttrs::NO_VALUE) {
if let Some(v) = value.filter(|_| enc_name.len() > 1) {
Err(ErrorKind::UnexpectedInlineValue.with_input(v.into()))
} else {
place.parse_from(p, attrs, "".as_ref(), "".as_ref(), &mut ())
}
} else if attrs.contains(ArgAttrs::REQUIRE_EQ) && !has_eq {
Err(ErrorKind::MissingEq.into())
} else if let Some(v) = value {
p.discard_short_args();
place.parse_from(p, attrs, v, "".as_ref(), &mut ())
} else {
p.next_value(attrs).ok_or_else(|| ErrorKind::MissingValue.into()).and_then(
|mut v| {
if attrs.contains(ArgAttrs::MAKE_LOWERCASE) {
v.make_ascii_lowercase();
}
place.parse_from(p, attrs, &v, "".as_ref(), &mut ())
},
)
}
.map_err(
#[cold]
|err| {
let desc = args_info.get_description(attrs.get_index());
err.with_arg_desc(desc).maybe_render_help(chain)
},
)?;
}
Arg::Unnamed(arg) => match chain.state.feed_unnamed(&arg, unnamed_idx, false) {
ControlFlow::Break((place, attrs)) => {
unnamed_idx += 1;
place.parse_from(p, attrs, &arg, chain.cmd_name, chain.ancestors)?;
}
ControlFlow::Continue(()) => {
return Err(ErrorKind::ExtraUnnamedArgument.with_input(arg));
}
},
Arg::DashDash => {
drop(arg);
drop(buf);
while let Some(arg) = p.iter.next() {
match chain.state.feed_unnamed(&arg, unnamed_idx, true) {
ControlFlow::Break((place, attrs)) => {
unnamed_idx += 1;
place.parse_from(p, attrs, &arg, chain.cmd_name, chain.ancestors)?;
}
ControlFlow::Continue(()) => {
return Err(ErrorKind::ExtraUnnamedArgument.with_input(arg));
}
}
}
return Ok(());
}
}
}
Ok(())
}
pub struct RawParser<'i> {
iter: &'i mut dyn Iterator<Item = OsString>,
next_short_idx: Option<NonZero<usize>>,
}
#[derive(Debug)]
enum Arg<'a> {
DashDash,
EncodedNamed(&'a str, bool, Option<&'a OsStr>),
Unnamed(OsString),
}
impl<'i> RawParser<'i> {
pub(crate) fn new(iter: &'i mut dyn Iterator<Item = OsString>) -> Self {
Self { iter, next_short_idx: None }
}
fn next_arg<'b>(&mut self, buf: &'b mut OsString) -> Result<Option<Arg<'b>>> {
#[cold]
fn fail_on_next_short_arg(rest: &OsStr) -> Error {
let bytes = rest.as_encoded_bytes();
for len in 2..=bytes.len().min(4) {
if let Ok(s) = std::str::from_utf8(&bytes[..len]) {
let mut dec_input = String::with_capacity(4);
dec_input.push('-');
dec_input.push_str(s);
return ErrorKind::UnknownNamedArgument.with_input(dec_input.into());
}
}
ErrorKind::InvalidUtf8.with_input(rest.into())
}
if let Some(pos) = self.next_short_idx.filter(|pos| pos.get() < buf.len()) {
let argb = buf.as_encoded_bytes();
let idx = pos.get();
let next_byte = std::str::from_utf8(&argb[idx..idx + 1]);
let short_arg = next_byte.map_err(|_| fail_on_next_short_arg(buf.index(idx..)))?;
self.next_short_idx = pos.checked_add(1);
let (has_eq, value) = match argb.get(idx + 1) {
Some(&b'=') => (true, Some(buf.index(idx + 2..))),
Some(_) => (false, Some(buf.index(idx + 1..))),
None => {
self.discard_short_args();
(false, None)
}
};
return Ok(Some(Arg::EncodedNamed(short_arg, has_eq, value)));
}
self.next_short_idx = None;
*buf = match self.iter.next() {
Some(raw) => raw,
None => return Ok(None),
};
if buf.starts_with("--") {
if buf.len() == 2 {
return Ok(Some(Arg::DashDash));
}
let rest = buf.index(2..);
let (name, has_eq, value) = match rest.split_once('=') {
Some((name, value)) => (name, true, Some(value)),
None => (rest, false, None),
};
let enc_name = if name.len() != 1 { name } else { buf.index(..3) };
let enc_name = enc_name
.to_str()
.ok_or_else(|| ErrorKind::InvalidUtf8.with_input(enc_name.into()))?;
Ok(Some(Arg::EncodedNamed(enc_name, has_eq, value)))
} else if buf.starts_with("-") && buf.len() != 1 {
self.next_short_idx = Some(NonZero::new(1).unwrap());
self.next_arg(buf)
} else {
Ok(Some(Arg::Unnamed(std::mem::take(buf))))
}
}
fn discard_short_args(&mut self) {
self.next_short_idx = None;
}
fn next_value(&mut self, attrs: ArgAttrs) -> Option<OsString> {
assert!(self.next_short_idx.is_none());
self.iter.next().filter(|raw| {
let raw = raw.as_encoded_bytes();
if raw == b"-" || !raw.starts_with(b"-") {
return true;
}
if attrs.contains(ArgAttrs::ACCEPT_HYPHEN_ANY) {
true
} else if attrs.contains(ArgAttrs::ACCEPT_HYPHEN_NUM) {
raw[1..].iter().all(|b| b.is_ascii_digit())
} else {
false
}
})
}
}