use super::declaration::{Positional, Tail, Verb, WrappedCommand};
use super::error::WrappedError;
const DYNAMIC: &str = "<dynamic>";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Known {
Literal,
Opaque,
OpaqueValue,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Word {
pub(crate) text: String,
pub(crate) known: Known,
}
impl Word {
pub(crate) fn literal(text: impl Into<String>) -> Self {
Self {
text: text.into(),
known: Known::Literal,
}
}
pub(crate) fn from_validation_text(text: impl Into<String>) -> Self {
let text = text.into();
let known = if text == DYNAMIC {
Known::Opaque
} else if text.starts_with("--") && text.ends_with(&format!("={DYNAMIC}")) {
Known::OpaqueValue
} else {
Known::Literal
};
Self { text, known }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct FlagUse {
pub(crate) flag_index: usize,
pub(crate) value: Option<String>,
pub(crate) value_known: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum Item {
Positional {
slot: usize,
value: String,
known: bool,
},
Flag(usize),
DashDash,
Undeclared(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Call {
pub(crate) verb_path: Vec<usize>,
pub(crate) flags: Vec<FlagUse>,
pub(crate) items: Vec<Item>,
pub(crate) uncertain: bool,
}
impl Call {
fn unjudgeable() -> Self {
Self {
verb_path: Vec::new(),
flags: Vec::new(),
items: Vec::new(),
uncertain: true,
}
}
pub(crate) fn verb<'d>(&self, declaration: &'d WrappedCommand) -> Option<&'d Verb> {
resolve_verb(declaration, &self.verb_path)
}
}
fn resolve_verb<'d>(declaration: &'d WrappedCommand, path: &[usize]) -> Option<&'d Verb> {
let Some((&first, rest)) = path.split_first() else {
return declaration.root.as_ref();
};
let mut verb = declaration.verbs.get(first)?;
for &index in rest {
verb = verb.verbs.get(index)?;
}
Some(verb)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ParseError {
pub(crate) error: WrappedError,
pub(crate) uncertain: bool,
}
pub(crate) fn parse(declaration: &WrappedCommand, words: &[Word]) -> Result<Call, ParseError> {
let Some((verb_path, mut index)) = select_verb(declaration, words)? else {
return Ok(Call::unjudgeable());
};
let Some(verb) = resolve_verb(declaration, &verb_path) else {
return Ok(Call::unjudgeable());
};
let scope = declaration.scope_of_path(&verb_path);
let mut call = Call {
verb_path,
flags: Vec::new(),
items: Vec::new(),
uncertain: false,
};
let mut past_dash_dash = false;
let mut slot = 0usize;
macro_rules! refuse {
($result:expr) => {
if let Err(error) = $result {
return Err(ParseError {
error,
uncertain: call.uncertain,
});
}
};
}
while index < words.len() {
let word = &words[index];
index += 1;
if past_dash_dash {
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
true,
));
continue;
}
match word.known {
Known::Opaque => {
call.uncertain = true;
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
false,
));
continue;
}
Known::Literal | Known::OpaqueValue => {}
}
if word.text == "--" {
past_dash_dash = true;
call.items.push(Item::DashDash);
continue;
}
if word.text.starts_with('-') {
refuse!(bind_flag(
declaration,
verb,
&scope,
&mut call,
words,
&mut index,
word,
));
continue;
}
refuse!(fill_positional(
declaration,
verb,
&scope,
&mut call,
&mut slot,
&word.text,
word.known,
false,
));
}
Ok(call)
}
fn select_verb(
declaration: &WrappedCommand,
words: &[Word],
) -> Result<Option<(Vec<usize>, usize)>, ParseError> {
if declaration.verbs.is_empty() {
return Ok(Some((Vec::new(), 0)));
}
let allowed = allowed_verbs(declaration);
let missing = |uncertain: bool| ParseError {
error: WrappedError::MissingVerb {
command: declaration.name.clone(),
allowed: allowed.clone(),
},
uncertain,
};
let Some(first) = words.first() else {
return match declaration.root {
Some(_) => Ok(Some((Vec::new(), 0))),
None => Err(missing(false)),
};
};
match first.known {
Known::Literal if !first.text.starts_with('-') => {
match declaration
.verbs
.iter()
.position(|v| v.name_or_root() == first.text)
{
Some(index) => {
let verb = &declaration.verbs[index];
let scope = declaration.scope_of(verb);
descend(declaration, scope, verb, vec![index], words, 1)
}
None if declaration.root.is_some() => Ok(Some((Vec::new(), 0))),
None => Err(ParseError {
error: WrappedError::UnknownVerb {
command: declaration.name.clone(),
word: first.text.clone(),
allowed,
},
uncertain: false,
}),
}
}
Known::Literal => match declaration.root {
Some(_) => Ok(Some((Vec::new(), 0))),
None => Err(missing(false)),
},
Known::Opaque | Known::OpaqueValue => match declaration.root {
Some(_) => Ok(Some((Vec::new(), 0))),
None => Ok(None),
},
}
}
fn descend(
declaration: &WrappedCommand,
scope: String,
verb: &Verb,
path: Vec<usize>,
words: &[Word],
consumed: usize,
) -> Result<Option<(Vec<usize>, usize)>, ParseError> {
if verb.verbs.is_empty() {
return Ok(Some((path, consumed)));
}
let allowed = allowed_verb_names(&verb.verbs);
let bare_node = || ParseError {
error: WrappedError::BareNode {
command: declaration.name.clone(),
scope: scope.clone(),
allowed: allowed.clone(),
},
uncertain: false,
};
let Some(next) = words.get(consumed) else {
return Err(bare_node());
};
match next.known {
Known::Literal if !next.text.starts_with('-') => {
match verb.verbs.iter().position(|v| v.name_or_root() == next.text) {
Some(index) => {
let child = &verb.verbs[index];
let child_scope = format!("{scope} {}", next.text);
let mut child_path = path;
child_path.push(index);
descend(declaration, child_scope, child, child_path, words, consumed + 1)
}
None => Err(ParseError {
error: WrappedError::UnknownChildVerb {
command: declaration.name.clone(),
scope,
word: next.text.clone(),
allowed,
},
uncertain: false,
}),
}
}
Known::Literal => Err(bare_node()),
Known::Opaque | Known::OpaqueValue => Ok(None),
}
}
fn bind_flag(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
call: &mut Call,
words: &[Word],
index: &mut usize,
word: &Word,
) -> Result<(), WrappedError> {
let (head, inline) = match word.text.strip_prefix("--").and_then(|rest| {
rest.find('=')
.map(|eq| (&word.text[..eq + 2], word.text[eq + 3..].to_string()))
}) {
Some((head, value)) => (head.to_string(), Some(value)),
None => (word.text.clone(), None),
};
let Some(flag_index) = verb
.flags
.iter()
.position(|flag| flag.matches(&head))
else {
if verb.tail == Tail::Forward {
call.items.push(Item::Undeclared(word.text.clone()));
return Ok(());
}
return Err(unknown_flag(declaration, verb, scope, &word.text, &head));
};
let flag = &verb.flags[flag_index];
if !flag.repeatable && call.flags.iter().any(|use_| use_.flag_index == flag_index) {
return Err(WrappedError::RepeatedFlag {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: flag.written_name(),
});
}
if !flag.takes_value {
if inline.is_some() {
return Err(WrappedError::UnexpectedFlagValue {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: head,
});
}
call.items.push(Item::Flag(call.flags.len()));
call.flags.push(FlagUse {
flag_index,
value: None,
value_known: true,
});
return Ok(());
}
let (value, value_known) = match inline {
Some(value) => (value, word.known == Known::Literal),
None => {
let Some(next) = words.get(*index) else {
return Err(WrappedError::MissingFlagValue {
command: declaration.name.clone(),
scope: scope.to_string(),
flag: head,
});
};
*index += 1;
(next.text.clone(), next.known == Known::Literal)
}
};
call.items.push(Item::Flag(call.flags.len()));
call.flags.push(FlagUse {
flag_index,
value: Some(value),
value_known,
});
Ok(())
}
fn unknown_flag(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
word: &str,
head: &str,
) -> WrappedError {
if let Some(separated) = clustered_shorts(verb, word) {
return WrappedError::ClusteredShort {
command: declaration.name.clone(),
scope: scope.to_string(),
word: word.to_string(),
separated,
};
}
if let Some(separated) = glued_short_value(verb, word) {
return WrappedError::GluedShortValue {
command: declaration.name.clone(),
scope: scope.to_string(),
word: word.to_string(),
separated,
};
}
WrappedError::UnknownFlag {
command: declaration.name.clone(),
scope: scope.to_string(),
word: head.to_string(),
allowed: allowed_flags(verb),
}
}
fn clustered_shorts(verb: &Verb, word: &str) -> Option<String> {
let rest = word.strip_prefix('-')?;
if rest.starts_with('-') || rest.chars().count() < 2 {
return None;
}
let mut separated = Vec::new();
for ch in rest.chars() {
let short = format!("-{ch}");
let flag = verb
.flags
.iter()
.find(|flag| flag.matches(&short))?;
if flag.takes_value {
return None;
}
separated.push(short);
}
Some(separated.join(" "))
}
fn glued_short_value(verb: &Verb, word: &str) -> Option<String> {
let rest = word.strip_prefix('-')?;
if rest.starts_with('-') || rest.chars().count() < 2 {
return None;
}
let mut chars = rest.chars();
let first = chars.next()?;
let short = format!("-{first}");
let flag = verb
.flags
.iter()
.find(|flag| flag.matches(&short))?;
if !flag.takes_value {
return None;
}
Some(format!("{short} {}", chars.as_str()))
}
#[allow(clippy::too_many_arguments)]
fn fill_positional(
declaration: &WrappedCommand,
verb: &Verb,
scope: &str,
call: &mut Call,
slot: &mut usize,
text: &str,
known: Known,
past_dash_dash: bool,
) -> Result<(), WrappedError> {
if let Some(Positional { many, .. }) = verb.positionals.get(*slot) {
call.items.push(Item::Positional {
slot: *slot,
value: text.to_string(),
known: known == Known::Literal,
});
if !many {
*slot += 1;
}
return Ok(());
}
match (verb.tail, past_dash_dash) {
(Tail::Forward, _) | (Tail::AfterDashDash, true) => {
call.items.push(Item::Undeclared(text.to_string()));
Ok(())
}
(Tail::AfterDashDash, false) => Err(WrappedError::UndeclaredPositional {
command: declaration.name.clone(),
scope: scope.to_string(),
word: text.to_string(),
}),
(Tail::Deny, _) => Err(WrappedError::UnexpectedArgument {
command: declaration.name.clone(),
word: text.to_string(),
}),
}
}
pub(crate) fn allowed_verbs(declaration: &WrappedCommand) -> Vec<String> {
allowed_verb_names(&declaration.verbs)
}
fn allowed_verb_names(verbs: &[Verb]) -> Vec<String> {
let mut allowed: Vec<String> = verbs.iter().map(|verb| verb.name_or_root().to_string()).collect();
allowed.sort();
allowed
}
pub(crate) fn allowed_flags(verb: &Verb) -> Vec<String> {
let mut flags: Vec<&super::declaration::Flag> = verb.flags.iter().collect();
flags.sort_by(|a, b| a.name.cmp(&b.name));
flags.iter().map(|flag| flag.allowed_spelling()).collect()
}