use crate::ported::modules::parameter::paramtypestr;
use crate::ported::params::{paramtab, setaparam};
use std::collections::HashMap;
use std::collections::VecDeque;
fn rewrite_follow(how: &str) -> String {
let mut s = how.to_string();
for (from, to) in [
("join", "-"),
("close", "-"),
("next", ""),
("split", ""),
("loose", "+"),
("assign", "="),
("none", ""),
] {
s = s.replacen(from, to, 1);
}
s
}
fn is_position(s: &str) -> bool {
if s == "*" {
return true;
}
!s.is_empty()
&& s.bytes().all(|b| b.is_ascii_digit())
&& s.parse::<u64>().map_or(false, |n| n >= 1)
}
pub fn _arg_compile(args: &[String]) -> i32 {
let _fn_scope = crate::compsys::ported::shared::FnScope::enter("_arg_compile");
let reply = match args.first() {
Some(r) if !r.is_empty() => r.clone(),
_ => return 1,
};
{
let tab = paramtab().read().unwrap();
if let Some(pm) = tab.get(&reply) {
if paramtypestr(pm).ends_with("-local") {
eprintln!("NAME CONFLICT: {}", reply);
return 1;
}
}
}
let mut rest: VecDeque<String> = args[1..].iter().cloned().collect();
let mut argspec: Vec<String> = Vec::new();
let mut helpspec: Vec<String> = Vec::new(); let mut prelude: Vec<String> = Vec::new();
while let Some(head) = rest.front() {
match head.as_str() {
"argument" | "help" | "option" => break, _ => {
prelude.push(rest.pop_front().unwrap());
}
}
}
while let Some(head) = rest.front().cloned() {
let mut amap: HashMap<String, String> = HashMap::new();
let mut dspec: Vec<String> = Vec::new();
match head.as_str() {
"argument" => {
rest.pop_front(); while let Some(k) = rest.front().cloned() {
if is_position(&k) {
amap.insert("position".to_string(), k);
rest.pop_front();
} else if k == "means" || k == "action" {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
amap.insert(k, v);
} else if k == "argument" || k == "option" || k == "help" {
break; } else {
eprintln!("SYNTAX ERROR at {}", vecdeque_join(&rest));
return 1;
}
}
if !amap.is_empty() {
argspec.push(format!(
"{}:{}:{}",
amap.get("position").cloned().unwrap_or_default(),
amap.get("means").cloned().unwrap_or_default(),
amap.get("action").cloned().unwrap_or_default(),
));
}
}
"option" => {
rest.pop_front(); let opt = rest.pop_front().unwrap_or_default();
amap.insert("option".to_string(), opt);
let mut dmap: HashMap<String, String> = HashMap::new(); let mut xor: Vec<String> = Vec::new(); 'opt_outer: while let Some(k) = rest.front().cloned() {
if amap.contains_key(&k) || dmap.contains_key("through") {
break;
}
match k.as_str() {
"follow" => {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
amap.insert("follow".to_string(), rewrite_follow(&v));
}
"explain" => {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
amap.insert("explain".to_string(), format!("[{}]", v));
}
"unless" => {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
xor.extend(v.split_whitespace().map(|s| s.to_string()));
}
"through" | "means" | "action" => {
while let Some(k2) = rest.front().cloned() {
if dmap.contains_key(&k2) {
break 'opt_outer;
}
match k2.as_str() {
"through" | "means" | "action" => {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
dmap.insert(k2, format!(":{}", v));
}
"argument" | "option" | "help" | "follow" | "explain"
| "unless" => {
break; }
_ => {
eprintln!("SYNTAX ERROR at {}", vecdeque_join(&rest));
return 1;
}
}
}
}
"argument" | "option" | "help" => break, _ => {
eprintln!("SYNTAX ERROR at {}", vecdeque_join(&rest));
return 1;
}
}
if !dmap.is_empty() {
dspec.push(format!(
"{}{}{}",
dmap.get("through").cloned().unwrap_or_default(),
non_empty_or_colon(dmap.get("means")),
non_empty_or_colon(dmap.get("action")),
));
}
}
if !amap.is_empty() {
let xor_prefix = if xor.is_empty() {
String::new()
} else {
format!("({})", xor.join(" "))
};
argspec.push(format!(
"{}{}{}{}{}",
xor_prefix,
amap.get("option").cloned().unwrap_or_default(),
amap.get("follow").cloned().unwrap_or_default(),
amap.get("explain").cloned().unwrap_or_default(),
dspec.join(" "),
));
}
}
"help" => {
rest.pop_front(); let pat = rest.pop_front().unwrap_or_default();
amap.insert("pattern".to_string(), pat);
while let Some(k) = rest.front().cloned() {
if amap.contains_key(&k) {
break;
}
match k.as_str() {
"means" | "action" => {
rest.pop_front();
let v = rest.pop_front().unwrap_or_default();
amap.insert(k, v);
}
"argument" | "option" | "help" => break, _ => {
eprintln!("SYNTAX ERROR at {}", vecdeque_join(&rest));
return 1;
}
}
}
if !amap.is_empty() {
helpspec.push(format!(
"{}:{}:{}",
amap.get("pattern").cloned().unwrap_or_default(),
amap.get("means").cloned().unwrap_or_default(),
amap.get("action").cloned().unwrap_or_default(),
));
}
}
_ => break,
}
}
let mut result: Vec<String> = Vec::new();
result.extend(prelude);
result.extend(argspec);
if !helpspec.is_empty() {
let mut first = String::from("-- ");
first.push_str(&helpspec[0]);
result.push(first);
result.extend(helpspec[1..].iter().cloned());
}
result.extend(rest.into_iter());
setaparam(&reply, result);
0 }
fn non_empty_or_colon(v: Option<&String>) -> String {
match v {
Some(s) if !s.is_empty() => s.clone(),
_ => ":".to_string(),
}
}
fn vecdeque_join(rest: &VecDeque<String>) -> String {
rest.iter().cloned().collect::<Vec<_>>().join(" ")
}
#[cfg(test)]
mod tests {
use super::*;
fn compile(spec: &[&str]) -> Vec<String> {
let args: Vec<String> = spec.iter().map(|s| s.to_string()).collect();
let rc = _arg_compile(&args);
assert_eq!(rc, 0, "compile should succeed");
crate::ported::params::getaparam(spec[0]).unwrap_or_default()
}
#[test]
fn empty_name_returns_one() {
let _g = crate::test_util::global_state_lock();
assert_eq!(_arg_compile(&[]), 1);
assert_eq!(_arg_compile(&["".to_string()]), 1);
}
#[test]
fn prelude_passes_through() {
let _g = crate::test_util::global_state_lock();
let out = compile(&["args", "-s", "-v"]);
assert_eq!(out, vec!["-s", "-v"]);
}
#[test]
fn argument_phrase_builds_pos_means_action() {
let _g = crate::test_util::global_state_lock();
let out = compile(&["args", "argument", "1", "means", "file", "action", "_files"]);
assert_eq!(out, vec!["1:file:_files"]);
}
#[test]
fn option_phrase_with_follow_and_explain() {
let _g = crate::test_util::global_state_lock();
let out = compile(&[
"args",
"option",
"-d",
"follow",
"close",
"means",
"debug level",
]);
assert_eq!(out, vec!["-d-:debug level:"]);
}
#[test]
fn option_with_explain_and_unless_xor() {
let _g = crate::test_util::global_state_lock();
let out = compile(&["args", "option", "-a", "explain", "foo", "unless", "-b"]);
assert_eq!(out, vec!["(-b)-a[foo]"]);
}
#[test]
fn help_phrase_and_trailing_marker() {
let _g = crate::test_util::global_state_lock();
let out = compile(&[
"args",
"help",
"*=name*",
"means",
"function name",
"action",
"->funcs",
]);
assert_eq!(out, vec!["-- *=name*:function name:->funcs"]);
}
#[test]
fn mixed_argument_then_help_phrase() {
let _g = crate::test_util::global_state_lock();
let out = compile(&[
"args",
"argument",
"means",
"profile file",
"action",
"_files",
"help",
"*=dirs*",
"action",
"_dir_list",
]);
assert_eq!(out, vec![":profile file:_files", "-- *=dirs*::_dir_list"]);
}
#[test]
fn syntax_error_returns_one() {
let _g = crate::test_util::global_state_lock();
let rc = _arg_compile(&[
"args".to_string(),
"argument".to_string(),
"means".to_string(),
"m".to_string(),
"bogus".to_string(),
]);
assert_eq!(rc, 1);
}
}