use {
crate::{CStr, cmdline::helpers::try_to_str, direct::argc_argv, iter::helpers::len},
std::{
cmp::{Ord, min},
collections::BTreeMap,
fmt::{Debug, Formatter, Result as FmtRes},
iter::Iterator,
marker::Copy,
mem::transmute,
ops::Fn,
option::Option::{self, None, Some},
ptr::{self, null},
result::Result::{self, Err, Ok},
slice,
str::Utf8Error
}
};
macro_rules! tri {
(str:$i:ident $e:expr) => {
match $e {
Ok(val) => val,
Err(err) => return Err(ParseError::InvalidStr($i, err))
}
};
(opt:$e:expr,$other:expr) => {
match $e {
Some(val) => val,
None => return (None, $other)
}
};
}
fn null_slice() -> *const [*const u8] {
ptr::slice_from_raw_parts(null(), 0)
}
const INDICATOR: char = '-';
pub struct IndexingParser {
index: BTreeMap<Ident, Argument>,
positionals: usize
}
impl IndexingParser {
#[allow(clippy::new_without_default, clippy::inline_always)]
#[must_use]
#[inline(always)]
pub fn new() -> IndexingParser {
IndexingParser { index: BTreeMap::new(), positionals: 0 }
}
pub fn reset(&mut self) {
self.index.clear();
}
pub fn parse(
&mut self,
rules: &[OptRule],
is_first_prog: impl Fn(&'static str) -> bool
) -> Result<(), ParseError> {
if !self.index.is_empty() {
return Ok(());
}
let (argc, argv) = argc_argv();
let len_1 = argc as usize;
let len = len_1 - 1;
let mut positionals = 0;
let mut i = 0;
let mut end_of_args = false;
let mut next = None;
unsafe {
loop {
let current_raw = argv.add(i);
let current = current_raw.read();
let str = if let Some(next) = next {
next
} else {
tri!(str:i CStr::from_ptr(current).to_stdlib().to_str())
};
if i < len {
let i = i + 1;
next = Some(
tri!(str:i CStr::from_ptr(current_raw.add(1).read()).to_stdlib().to_str())
);
}
if i == 0 && is_first_prog(str) {
self.index.insert(Ident::__Prog, Argument::ProgFlagOrPos(str));
} else if end_of_args {
self.push_positional(&mut positionals, str);
} else {
let mut chars = str.chars();
match (chars.next(), chars.next(), chars.next()) {
(Some(INDICATOR), Some(INDICATOR), Some(_)) => {
self.push_long(str, current_raw, rules, len - i, &mut i, next);
}
(Some(INDICATOR), Some(INDICATOR), None) => {
end_of_args = true;
}
(Some(INDICATOR), Some(_), _) => {
self.push_short(str, current_raw, rules, len - i, &mut i, next);
}
_ => {
self.push_positional(&mut positionals, str);
}
}
}
i += 1;
if i == len_1 {
self.positionals = positionals;
return Ok(());
}
}
}
}
#[must_use]
#[inline]
pub fn prog_name(&self) -> Option<&'static str> {
self.index.get(&Ident::__Prog).map(Argument::opt)
}
#[must_use]
#[inline]
pub const fn positional_count(&self) -> usize {
self.positionals
}
#[must_use]
#[inline]
pub fn positional(&self, n: usize) -> Option<&'static str> {
if n >= self.positionals {
return None;
}
for (id, arg) in &self.index {
match id {
Ident::Positional(p_n) if *p_n == n => {
return Some(arg.opt());
}
_ => {}
}
}
None
}
#[must_use]
#[inline]
pub fn flag(&self, name: &'static str) -> bool {
for id in self.index.keys() {
match id {
Ident::Option(rule_name) if *rule_name == name => {
return true;
}
_ => {}
}
}
false
}
#[must_use]
#[inline]
pub fn option(&self, name: &'static str) -> (Option<OptValues>, bool) {
for (id, arg) in &self.index {
match id {
Ident::Option(rule_name) if *rule_name == name => {
let val = tri!(opt:arg.val(), true);
return (
Some(OptValues {
cur: val.cast::<*const u8>(),
end: unsafe { val.cast::<*const u8>().add((&*val).len()) },
offset: arg.val_offset().unwrap_or(0)
}),
true
);
}
_ => {}
}
}
(None, false)
}
#[allow(clippy::inline_always)]
#[inline(always)]
fn push_positional(&mut self, positionals: &mut usize, s: &'static str) {
self.index.insert(Ident::Positional(*positionals), Argument::ProgFlagOrPos(s));
*positionals += 1;
}
#[inline]
fn push_long(
&mut self,
s: &'static str,
raw: *const *const u8,
rules: &[OptRule],
remaining: usize,
i: &mut usize,
next_peek: Option<&str>
) {
let eq_form = s.chars().position(|c| c == '=');
for rule in rules {
match rule.long() {
Some(rule_s) if rule_s == eq_form.map_or_else(|| &s[2..], |eq| &s[2..eq]) => {
let (val, val_offset) = eq_form.map_or_else(
|| (IndexingParser::parse_vals(raw, rule, remaining, i, next_peek), 0),
|i| (ptr::slice_from_raw_parts(raw, 1), i + 1)
);
self.index.insert(
Ident::Option(rule.name()),
Argument::new_maybe_opt(s, val, val_offset)
);
}
_ => {}
}
}
}
#[inline]
fn push_short(
&mut self,
s: &'static str,
raw: *const *const u8,
rules: &[OptRule],
remaining: usize,
i: &mut usize,
next_peek: Option<&str>
) {
let cut = &s[1..];
for (c_i, c) in cut.char_indices() {
for rule in rules {
match rule.short() {
Some(rule_c) if rule_c == c => {
self.index.insert(
Ident::Option(rule.name()),
Argument::new_maybe_opt(
&cut[c_i..=c_i],
IndexingParser::parse_vals(raw, rule, remaining, i, next_peek),
0
)
);
}
_ => {}
}
}
}
}
#[inline]
fn next_is_special(peek: Option<&str>) -> bool {
peek.map_or(false, |s| {
let mut chars = s.chars();
match (chars.next(), chars.next(), chars.next()) {
(Some('-'), Some('-'), Some(_)) | (Some('-'), Some(_), _) => true,
_ => false
}
})
}
#[inline]
fn parse_vals(
raw: *const *const u8,
rule: &OptRule,
remaining: usize,
i: &mut usize,
next_peek: Option<&str>
) -> *const [*const u8] {
if IndexingParser::next_is_special(next_peek) {
return null_slice();
}
match rule.val_count() {
0 => null_slice(),
n => unsafe {
let cnt = min(n, remaining);
if cnt == 0 {
return null_slice();
}
*i += cnt;
ptr::slice_from_raw_parts(raw.add(1), cnt)
}
}
}
fn write_vals(f: &mut Formatter<'_>, vals: *const [*const u8]) -> FmtRes {
let slice = unsafe { &*vals };
write!(f, "[")?;
for (i, &p) in slice.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
match try_to_str(p) {
Some(s) => write!(f, "{:?}", s)?,
None => write!(f, "{:p}", p)?
}
}
write!(f, "]")
}
fn debug_alt(&self, f: &mut Formatter<'_>) -> FmtRes {
writeln!(f, "IndexingParser(")?;
for (id, arg) in &self.index {
match id {
Ident::__Prog => writeln!(f, " Program executable: {}", arg.opt())?,
Ident::Positional(n) => writeln!(f, " Positional #{}: {}", n, arg.opt())?,
Ident::Option(name) => {
if let Some(val) = arg.val() {
write!(f, " ?Option?: \"{}\": ", name)?;
IndexingParser::write_vals(f, val)?;
writeln!(f)?;
} else {
writeln!(f, " ?Flag?: \"{}\"", name)?;
}
}
}
}
writeln!(f, ")")
}
fn debug_norm(&self, f: &mut Formatter<'_>) -> FmtRes {
write!(f, "IndexingParser(")?;
let mut first = true;
for (id, arg) in &self.index {
if first {
first = false;
} else {
write!(f, ", ")?;
}
match id {
Ident::__Prog => write!(f, "program={:?}", arg.opt())?,
Ident::Positional(n) => write!(f, "{}={:?}", n, arg.opt())?,
Ident::Option(name) => {
if let Some(val) = arg.val() {
write!(f, "{}=", name)?;
IndexingParser::write_vals(f, val)?;
} else {
write!(f, "?flag?=\"{}\"", name)?;
}
}
}
}
write!(f, ")")
}
}
impl Debug for IndexingParser {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtRes {
if self.index.is_empty() {
return write!(f, "IndexingParser(unparsed)");
}
if f.alternate() {
return self.debug_alt(f);
}
self.debug_norm(f)
}
}
pub struct OptRule {
name: &'static str,
long: (*const u8, usize),
short: char,
val_count: usize
}
impl OptRule {
#[must_use]
pub const fn new(name: &'static str) -> OptRule {
OptRule { name, long: (null(), 0), short: '\0', val_count: 0 }
}
#[must_use]
pub const fn new_auto_long(name: &'static str) -> OptRule {
OptRule { name, long: (name.as_ptr(), name.len()), short: '\0', val_count: 0 }
}
#[must_use]
pub const fn new_auto(name: &'static str) -> OptRule {
OptRule {
name,
long: (name.as_ptr(), name.len()),
short: {
const CONT_MASK: u8 = 0b0011_1111;
#[inline]
const fn utf8_acc_cont_byte(ch: u32, byte: u8) -> u32 {
(ch << 6) | (byte & CONT_MASK) as u32
}
#[inline]
const fn first_char(bytes: &[u8]) -> u32 {
let x = bytes[0];
if x < 128 {
return x as u32;
}
let init = (x & (0x7F >> 2)) as u32;
let y = bytes[1];
let mut ch = utf8_acc_cont_byte(init, y);
if x >= 0xE0 {
let y_z = utf8_acc_cont_byte((y & CONT_MASK) as u32, bytes[2]);
ch = init << 12 | y_z;
if x >= 0xF0 {
ch = (init & 7) << 18 | utf8_acc_cont_byte(y_z, bytes[3]);
}
}
ch
}
const unsafe fn transmute<Src: Copy, Dst: Copy>(s: Src) -> Dst {
*(&s as *const Src).cast::<Dst>()
}
unsafe {
#[allow(unnecessary_transmutes)]
transmute::<u32, char>(first_char(name.as_bytes()))
}
},
val_count: 0
}
}
#[must_use]
pub const fn set_long(mut self, long: &'static str) -> OptRule {
self.long = (long.as_ptr(), long.len());
self
}
#[must_use]
pub const fn set_short(mut self, short: char) -> OptRule {
self.short = short;
self
}
#[must_use]
pub const fn set_val_count(mut self, val_count: usize) -> OptRule {
self.val_count = val_count;
self
}
#[must_use]
pub const fn name(&self) -> &'static str {
self.name
}
#[must_use]
pub fn long(&self) -> Option<&'static str> {
if self.long.1 == 0 {
None
} else {
Some(unsafe {
#[allow(clippy::transmute_bytes_to_str)]
transmute::<&[u8], &str>(slice::from_raw_parts(self.long.0, self.long.1))
})
}
}
#[must_use]
pub const fn short(&self) -> Option<char> {
if self.short == '\0' { None } else { Some(self.short) }
}
#[must_use]
pub const fn val_count(&self) -> usize {
self.val_count
}
}
#[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
enum Ident {
__Prog,
Positional(usize),
Option(&'static str)
}
enum Argument {
ProgFlagOrPos(&'static str),
Opt {
opt: &'static str,
val: *const [*const u8],
val_offset: usize
}
}
#[allow(clippy::inline_always)]
impl Argument {
fn new_maybe_opt(opt: &'static str, val: *const [*const u8], val_offset: usize) -> Argument {
if val.is_null() {
Argument::ProgFlagOrPos(opt)
} else {
Argument::Opt { opt, val, val_offset }
}
}
#[inline(always)]
const fn opt(&self) -> &'static str {
match self {
Argument::ProgFlagOrPos(opt) | Argument::Opt { opt, .. } => opt
}
}
#[inline(always)]
const fn val(&self) -> Option<*const [*const u8]> {
match self {
Argument::ProgFlagOrPos(_) => None,
Argument::Opt { val, .. } => Some(*val)
}
}
#[inline(always)]
const fn val_offset(&self) -> Option<usize> {
match self {
Argument::ProgFlagOrPos(_) => None,
Argument::Opt { val_offset, .. } => Some(*val_offset)
}
}
}
#[derive(Debug)]
pub enum ParseError {
InvalidStr(usize, Utf8Error)
}
pub struct OptValues {
cur: *const *const u8,
end: *const *const u8,
offset: usize
}
impl Iterator for OptValues {
type Item = &'static str;
#[allow(clippy::inline_always)]
#[inline(always)]
fn next(&mut self) -> Option<&'static str> {
if self.cur == self.end {
return None;
}
let p = self.cur;
self.cur = unsafe { self.cur.add(1) };
try_to_str(unsafe { p.read().add(self.offset) })
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = unsafe { len(self.cur, self.end) };
(len, Some(len))
}
}