prototype.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. // The definition of the Prototype class which specifies the translation
  2. // between the arguments passed to and the parameters received by a function
  3. // or the argument function of .scope
  4. //
  5. // Copyright (C) 2015-2019 Samuel Newbold
  6. #include <set>
  7. #include <string>
  8. #include <vector>
  9. #include <list>
  10. #include <map>
  11. #include "arg_spec.h"
  12. #include "rwsh_stream.h"
  13. #include "variable_map.h"
  14. #include "argm.h"
  15. #include "arg_script.h"
  16. #include "executable.h"
  17. #include "prototype.h"
  18. void Parameter_group::p_elipsis(Variable_map& locals,
  19. Argm::const_iterator& f_arg, int& available, const std::string& name,
  20. const std::string* flag, int needed, enum Dash_dash_type dash_dash,
  21. bool is_reassign, Error_list& exceptions) const {
  22. if (flag) {
  23. locals.append_word("-*", *f_arg, is_reassign);
  24. if (*flag != name) locals.append_word(*flag, *f_arg, is_reassign);}
  25. for (f_arg++; available > needed; --available, f_arg++) {
  26. if ((*f_arg)[0] == '-' && !dash_dash && f_arg->length() > 1)
  27. exceptions.add_error(Exception(Argm::Flag_in_elipsis, *f_arg, str()));
  28. locals.append_word(name, *f_arg, is_reassign);
  29. if (flag) {
  30. locals.append_word("-*", *f_arg, is_reassign);
  31. if (*flag != name) locals.append_word(*flag, *f_arg, is_reassign);}}}
  32. Parameter_group::Parameter_group(Argm::const_iterator& focus,
  33. Argm::const_iterator end,
  34. std::set<std::string>& parameter_names) :
  35. elipsis(-2), has_argfunction(false), names(), required((*focus)[0] != '[') {
  36. bool group_end;
  37. do {
  38. group_end = (*focus)[focus->length()-1] == ']';
  39. bool group_begin = (*focus)[0] == '[';
  40. std::string name(
  41. focus->substr(group_begin, focus->length() - group_begin - group_end));
  42. if (name[0] != '.') names.push_back(name);
  43. else if (name == "...")
  44. if (!parameter_names.insert(name).second)
  45. throw Exception(Argm::Duplicate_parameter, name);
  46. else elipsis = names.size()-1;
  47. else if (name == ".{argfunction}") {
  48. has_argfunction = true;
  49. names.push_back(name);}
  50. else throw Exception(Argm::Fixed_argument, name);}
  51. while (!required && !group_end && ++focus != end);
  52. if (!required && !group_end) {
  53. std::string gs(str());
  54. throw Exception(Argm::Mismatched_bracket, gs.substr(0, gs.length()-1));}
  55. for (auto i: names)
  56. if (!parameter_names.insert(i).second && i != "--")
  57. throw Exception(Argm::Duplicate_parameter, i);
  58. else if (names.size() > 1)
  59. if (i == "-*" || i == "-?")
  60. throw Exception(Argm::Dash_star_argument, str());
  61. else if (i == "--") throw Exception(Argm::Dash_dash_argument, str());
  62. else;
  63. else;}
  64. void Parameter_group::arg_to_param(int& available, int& needed,
  65. std::string& missing,
  66. Argm::const_iterator& f_arg,
  67. const Argm::const_iterator end,
  68. const std::string* flag,
  69. const std::string& elipsis_var,
  70. enum Dash_dash_type dash_dash,
  71. bool is_reassign,
  72. Variable_map& locals,
  73. Error_list& exceptions) const {
  74. available -= names.size();
  75. if (required) --needed;
  76. if (elipsis == -1) {
  77. locals.set(elipsis_var, word_from_value(locals.get(elipsis_var)));
  78. p_elipsis(locals, --f_arg, available, elipsis_var, flag, needed,
  79. dash_dash, is_reassign, exceptions);}
  80. std::vector<std::string>::difference_type k = 0;
  81. for (; f_arg != end &&
  82. k < (std::vector<std::string>::difference_type) names.size(); k++)
  83. if (elipsis == k) {
  84. locals.param(elipsis_var, word_from_value(*f_arg), is_reassign);
  85. p_elipsis(locals, f_arg, available, elipsis_var, flag, needed, dash_dash,
  86. is_reassign, exceptions);}
  87. else if (flag) {
  88. locals.append_word("-*", *f_arg, is_reassign);
  89. if (*flag != names[k]) locals.append_word(*flag, *f_arg, is_reassign);
  90. locals.param_or_append_word(names[k], *f_arg++, is_reassign);}
  91. else locals.param(names[k], *f_arg++, is_reassign);
  92. if (f_arg == end)
  93. while(k < (std::vector<std::string>::difference_type) names.size()) {
  94. missing += (missing.length()?" ":"") + names[k];
  95. locals.add_undefined(names[k++], is_reassign);}}
  96. void Parameter_group::add_undefined_params(Variable_map& locals,
  97. bool is_reassign) const {
  98. for (auto j: names) locals.add_undefined(j, is_reassign);}
  99. std::string Parameter_group::str() const {
  100. if (!names.size())
  101. if (required) return "...";
  102. else return "[...]";
  103. else if (required) return names[0] + (elipsis? "": " ...");
  104. else {
  105. std::string result("[");
  106. if (elipsis == -1) result.append("... ");
  107. for (std::vector<std::string>::difference_type j = 0;
  108. j < (std::vector<std::string>::difference_type) names.size(); ++j)
  109. result.append((j? " ": "") + names[j] + (elipsis == j? " ...": ""));
  110. return result + "]";}}
  111. Prototype::Prototype(void) :
  112. bare_dash_dash(false), dash_dash_position(-1), elipsis_var(""),
  113. flag_options(), flags(ALL), parameter_names{"--"},
  114. positional(), required_argc(),
  115. exclude_argfunction(true), required_argfunction(false) {}
  116. Prototype::Prototype(const Argv& parameters) :
  117. bare_dash_dash(false), dash_dash_position(-1), elipsis_var(""),
  118. flag_options(), flags(ALL), parameter_names{"--"},
  119. positional(), required_argc(),
  120. exclude_argfunction(true), required_argfunction(false) {
  121. bool has_elipsis = false;
  122. for (auto fp = parameters.begin(); fp != parameters.end(); ++fp) {
  123. Parameter_group group(fp, parameters.end(), parameter_names);
  124. if (has_elipsis && !group.required && !group.has_argfunction)
  125. throw Exception(Argm::Post_elipsis_option, group.str());
  126. else if (group.elipsis == -1) {
  127. if (!positional.size())
  128. throw Exception(Argm::Elipsis_first_arg, group.str());
  129. has_elipsis = true;
  130. elipsis_var = positional.back().names.back();
  131. if (group.names.size()) positional.push_back(group);
  132. else if (positional.back().names.size() != 1)
  133. throw Exception(Argm::Elipsis_out_of_option_group,
  134. positional.back().str());
  135. else positional.back().elipsis = 0;}
  136. else if (group.names[0] == "--") {
  137. if (dash_dash_position != -1)
  138. throw Exception(Argm::Duplicate_parameter, "--");
  139. dash_dash_position = positional.size();
  140. bare_dash_dash = group.required;
  141. if (!dash_dash_position && (flag_options.size() || flags == SOME))
  142. throw Exception(Argm::Ambiguous_prototype_dash_dash, str());}
  143. else if (group.names[0] == ".{argfunction}") {
  144. if (group.required) required_argfunction = true;
  145. exclude_argfunction = false;}
  146. else if (group.names[0] == "-?" || group.names[0] == "-*") {
  147. flags = SOME;
  148. parameter_names.insert({"-*", "-?"});}
  149. else if (group.required || group.names[0][0] != '-' ||
  150. group.names[0].length() == 1) {
  151. required_argc += group.required;
  152. positional.push_back(group);}
  153. else if (dash_dash_position != -1)
  154. throw Exception(Argm::Post_dash_dash_flag, group.str());
  155. else {
  156. flag_options[group.names[0]] = group;
  157. parameter_names.insert("-*");}
  158. if (group.elipsis >= 0) {
  159. has_elipsis = true;
  160. elipsis_var = group.names[group.elipsis];}}}
  161. void Prototype::arg_to_param(const Argv& argv, Variable_map& locals,
  162. Error_list& exceptions) const {
  163. arg_to_param_internal(argv, false, locals, exceptions);}
  164. void Prototype::arg_to_param_internal(const Argv& argv, bool is_reassign,
  165. Variable_map& locals, Error_list& exceptions) const {
  166. enum Dash_dash_type dash_dash = dash_dash_position? UNSEEN:
  167. bare_dash_dash? BARE: BRACKET;
  168. if (dash_dash == BRACKET) locals.add_undefined("--", is_reassign);
  169. if (flags == SOME)
  170. locals.param("-*","", is_reassign), locals.param("-?","", is_reassign);
  171. else if (flag_options.size()) locals.param("-*", "", is_reassign);
  172. for (auto j: flag_options)
  173. j.second.add_undefined_params(locals, is_reassign);
  174. int needed = required_argc;
  175. std::string missing;
  176. auto f_arg = argv.begin()+1;
  177. auto param = positional.begin();
  178. for (int available = argv.size()-1; f_arg != argv.end();)
  179. if ((*f_arg)[0] == '-' && f_arg->length() > 1 && dash_dash != BARE) {
  180. auto h = flag_options.find(*f_arg);
  181. if (dash_dash == BRACKET && *f_arg != "--") {
  182. --available;
  183. exceptions.add_error(Exception(Argm::Tardy_flag, *f_arg++));}
  184. else if (h != flag_options.end())
  185. h->second.arg_to_param(available, needed, missing, f_arg, argv.end(),
  186. &h->second.names[0], elipsis_var, dash_dash,
  187. is_reassign, locals, exceptions);
  188. else {
  189. if (*f_arg == "--") {
  190. locals.param("--", "--", is_reassign);
  191. dash_dash = BARE;}
  192. else if (flags == ALL)
  193. exceptions.add_error(Exception(Argm::Unrecognized_flag, *f_arg, str()));
  194. if (flags == SOME) locals.append_word("-?", *f_arg, is_reassign);
  195. if (flag_options.size() || flags == SOME)
  196. locals.append_word("-*", *f_arg, is_reassign);
  197. ++f_arg, --available;}}
  198. else if (param == positional.end()) break;
  199. else {
  200. if (param->required || available > needed)
  201. param->arg_to_param(available, needed, missing, f_arg, argv.end(),
  202. nullptr, elipsis_var, dash_dash, is_reassign,
  203. locals, exceptions);
  204. else param->add_undefined_params(locals, is_reassign);
  205. if (++param - positional.begin() == dash_dash_position)
  206. dash_dash = bare_dash_dash? BARE: BRACKET;}
  207. if (param != positional.end()) {
  208. if (param->elipsis == -1) {
  209. const std::string& var((param-1)->names.back());
  210. if (!locals.simple_exists(var)) locals.add_undefined(var, is_reassign);
  211. else locals.set(var, word_from_value(locals.get(var)));}
  212. while (param != positional.end()) {
  213. if (param->required)
  214. missing += (missing.length()?" ":"") + param->names[0];
  215. param++->add_undefined_params(locals, is_reassign);}}
  216. if (f_arg != argv.end() || needed || missing.length())
  217. bad_args(missing, locals, f_arg, argv.end(), exceptions);
  218. if (exceptions.size()) locals.bless_unused_vars_without_usage();}
  219. void Prototype::bad_args(std::string& missing, Variable_map& locals,
  220. Argm::const_iterator f_arg, Argm::const_iterator end,
  221. Error_list& exceptions) const {
  222. std::string assigned;
  223. for (auto k: parameter_names) if (locals.exists_without_check(k))
  224. assigned += (assigned.length()? " (": "(") + k + " " +
  225. word_from_value(locals.get(k)) + ")";
  226. std::string unassigned;
  227. while (f_arg != end) unassigned += (unassigned.length()?" ":"") + *f_arg++;
  228. exceptions.add_error(Exception(Argm::Bad_args, str(), assigned, missing,
  229. unassigned));}
  230. void Prototype::reassign(const Argv& argv,
  231. Variable_map& locals, Error_list& exceptions) const {
  232. arg_to_param_internal(argv, true, locals, exceptions);}
  233. std::string Prototype::str() const {
  234. std::string result;
  235. if (flags == SOME) result = "[-?] ";
  236. for (auto i: flag_options)
  237. result.append(i.second.str() + " ");
  238. for (int i=0; i <= (int) positional.size(); ++i) {
  239. if (dash_dash_position == i)
  240. if (bare_dash_dash) result.append("-- ");
  241. else result.append("[--] ");
  242. else;
  243. if (i < (int)positional.size()) result.append(positional[i].str() + " ");}
  244. if (required_argfunction) result.append(".{argfunction} ");
  245. else if (!exclude_argfunction) result.append("[.{argfunction}] ");
  246. return result.substr(0, result.length()-1);}
  247. void Parameter_group::bless_unused_vars(Variable_map* vars) const {
  248. // this is not thoroughly tested in the test suite
  249. for (auto j: names) vars->used_vars_insert(j);}
  250. char Parameter_group::unused_flag_var_check(Variable_map* vars,
  251. Error_list& errors) const {
  252. bool unused_flag = false;
  253. if (vars->used_vars_contains(names[0])) bless_unused_vars(vars);
  254. else if (names.size() == 1)
  255. if (vars->checked_vars_contains(names[0]) || vars->locals_listed);
  256. else {
  257. unused_flag = vars->exists_without_check(names[0]);
  258. errors.add_error(Exception(
  259. unused_flag? Argm::Unused_variable: Argm::Unchecked_variable,
  260. names[0]));}
  261. else if (vars->exists_without_check(names[0])) {
  262. for (auto j=names.begin()+1; j != names.end(); ++j)
  263. if (!vars->used_vars_contains(*j)) {
  264. unused_flag = true;
  265. errors.add_error(Exception(Argm::Unused_variable, *j));
  266. vars->used_vars_insert(*j);}
  267. if (unused_flag)
  268. errors.add_error(Exception(Argm::Unused_variable, names[0]));}
  269. else if (!vars->locals_listed) {
  270. bool checked = false;
  271. for (auto j: names) if (vars->checked_vars_contains(j)) checked = true;
  272. if (!checked) {
  273. for (auto j: names)
  274. errors.add_error(Exception(Argm::Unchecked_variable, j));
  275. return true;}}
  276. vars->used_vars_insert(names[0]);
  277. return unused_flag;}
  278. void Parameter_group::unused_pos_var_check(Variable_map* vars,
  279. Error_list& errors) const {
  280. if (vars->exists_without_check(names[0]))
  281. for (auto j: names)
  282. if (vars->exists_without_check(j) && !vars->used_vars_contains(j)) {
  283. errors.add_error(Exception(Argm::Unused_variable, j));
  284. vars->used_vars_insert(j);}
  285. else;
  286. else if (!vars->locals_listed) {
  287. bool checked = false;
  288. for (auto j: names) if (vars->checked_vars_contains(j)) checked = true;
  289. if (!checked) for (auto j: names)
  290. errors.add_error(Exception(Argm::Unchecked_variable, j));}}
  291. void Prototype::unused_var_check(Variable_map* vars, Error_list& errors) const {
  292. if (!vars->usage_checked) vars->usage_checked = true;
  293. else errors.add_error(Exception(Argm::Internal_error,
  294. "variable map usage checked multiple times"));
  295. bool unused_flag = false;
  296. if (vars->used_vars_contains("-*")) for (auto i: flag_options)
  297. i.second.bless_unused_vars(vars);
  298. else {
  299. for (auto i: flag_options)
  300. unused_flag |= i.second.unused_flag_var_check(vars, errors);
  301. if (flags == SOME && !vars->used_vars_contains("-?")) {
  302. unused_flag = true;
  303. errors.add_error(Exception(Argm::Unused_variable, "-?"));}}
  304. if (unused_flag) errors.add_error(Exception(Argm::Unused_variable, "-*"));
  305. vars->used_vars_insert("-*"); // in the absent else case $-* is not defined
  306. vars->used_vars_insert("-?"); // in the absent else case $-? is not defined
  307. for (auto i: positional) i.unused_pos_var_check(vars, errors);
  308. for (auto j: vars->locals())
  309. if (!vars->used_vars_contains(j) && !vars->undefined_vars_contains(j)) {
  310. errors.add_error(Exception(Argm::Unused_variable, j));
  311. vars->used_vars_insert(j);}}