Options.Mod 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. MODULE Options; (** AUTHOR "staubesv"; PURPOSE "Command line options parsing"; *)
  2. (*
  3. * Simple framework that parses command line options.
  4. *
  5. * Usage:
  6. *
  7. * 1. Create Option object instance
  8. *
  9. * NEW(options);
  10. *
  11. * 2. Add options of type Flag, String or Integer
  12. *
  13. * options.Add("h", "help", Options.Flag); (* -h / --help option flags *)
  14. * options.Add("s", "string", Options.String); (* -s=Hello / --string="Hello World" *)
  15. * options.Add("i", "integer", Options.Integer); (* -i=76H / --integer=999 *)
  16. *
  17. * 3. Parse options at the current position of the context argument stream (this will skip whitespace and options on the stream)
  18. *
  19. * IF options.Parse(context.arg, context.error) THEN (* some useful work *) END;
  20. *
  21. * Note: Parse will output an error message on the context error stream if option parsing fails
  22. *
  23. * 4. Access options
  24. *
  25. * IF options.GetFlag("help") THEN (* flag -h or --help is set *) END;
  26. * IF options.GetString("string", string_variable) THEN
  27. * (* If -s or --string was set, read the string argument into the string-variable *)
  28. * END;
  29. *
  30. *
  31. * Options = [ "-" Option [ {WhiteSpace "-" Option} ] ]
  32. * Option = "-" NameOption | CharOption
  33. * NameOption = Name [Assignment]
  34. * CharOption = Char [Assignment] | Flags
  35. * Flags = = Char {Char}
  36. * Assignment = "=" (EnquotedString | Name | Char)
  37. * Name = Char Char {Char}
  38. * EnquotedString = " anyChars except quote " | ' anyChars except apostroph '
  39. * Char = (32 < ORD(CHAR) < 127) & (ch # Assignment) & (ch # OptionDelimiter)
  40. *)
  41. IMPORT
  42. KernelLog, Streams, Strings, Commands;
  43. CONST
  44. (** Option Types *)
  45. Flag* = 0;
  46. String* = 1;
  47. Integer* = 2;
  48. Real*=3;
  49. Unknown = -1;
  50. MaxOptions = 64;
  51. OptionDelimiter = "-";
  52. Assignment = "=";
  53. Invalid = -1;
  54. TYPE
  55. Name* = ARRAY 32 OF CHAR;
  56. Parameter* = ARRAY 256 OF CHAR;
  57. Option = RECORD
  58. isSet : BOOLEAN;
  59. timestamp : LONGINT;
  60. ch : CHAR; (* single character name *)
  61. name : Name; (* multi character name *)
  62. type : LONGINT; (* Flag, String or Integer *)
  63. value : LONGINT; (* Integer value if type = Integer *)
  64. rvalue: LONGREAL; (*real value if type = real*)
  65. string : Parameter; (* String value if type = String *)
  66. END;
  67. TYPE
  68. Options* = OBJECT
  69. VAR
  70. options : ARRAY MaxOptions OF Option;
  71. nofOptions : LONGINT;
  72. arg : Streams.Reader;
  73. error : Streams.Writer;
  74. setError : BOOLEAN;
  75. flagUnknownOptions : BOOLEAN;
  76. timestamp : LONGINT;
  77. PROCEDURE &Init*;
  78. BEGIN
  79. timestamp := 0;
  80. Reset;
  81. END Init;
  82. (** Add option declaration.
  83. - Duplicate names are not allowed!
  84. - Numbers are not allowed as option character or as first character of an option name *)
  85. PROCEDURE Add*(ch : CHAR; CONST name : Name; type : LONGINT);
  86. VAR index : LONGINT; char : Name;
  87. BEGIN {EXCLUSIVE}
  88. IF (("0" <= ch) & (ch <= "9")) OR (("0" <= name[0]) & (name[0] <= "9")) THEN
  89. KernelLog.String("Command implementation error: Numbers are not allowed as first character of an option name. Ignore option ");
  90. KernelLog.Ln;
  91. RETURN;
  92. END;
  93. char[0] := ch; char[1] := 0X;
  94. index := FindOption(char);
  95. IF (index = Invalid) THEN index := FindOption(name); END;
  96. IF (index = Invalid) THEN
  97. IF (nofOptions < MaxOptions-1) THEN
  98. options[nofOptions].isSet := FALSE;
  99. options[nofOptions].ch := ch;
  100. options[nofOptions].name := name;
  101. options[nofOptions].type := type;
  102. INC(nofOptions);
  103. ELSE
  104. KernelLog.String("Command implementation error: Maximum number of option declarations exceeded. Ignore option ");
  105. KernelLog.Ln;
  106. END;
  107. ELSE
  108. KernelLog.String("Command implementation error: Duplicate option declaration. Ignore option .");
  109. KernelLog.Ln;
  110. END;
  111. END Add;
  112. (** Check whether an option of type Flag is set *)
  113. PROCEDURE GetFlag*(CONST name : Name) : BOOLEAN;
  114. VAR index : LONGINT;
  115. BEGIN {EXCLUSIVE}
  116. index := FindOption(name);
  117. IF (index # Invalid) THEN
  118. IF (options[index].type = Flag) THEN
  119. RETURN options[index].isSet;
  120. ELSE
  121. WrongUsage(options[index]);
  122. END;
  123. END;
  124. RETURN FALSE;
  125. END GetFlag;
  126. PROCEDURE SetFlag*(ch : CHAR; CONST name : Name) : BOOLEAN;
  127. BEGIN {EXCLUSIVE}
  128. RETURN SetFlagIntern(ch, name, FALSE);
  129. END SetFlag;
  130. (** Check whether an option of type Integer is set and retrieve its value if so *)
  131. PROCEDURE GetInteger*( CONST name : Name; VAR integer : LONGINT) : BOOLEAN;
  132. VAR index : LONGINT;
  133. BEGIN {EXCLUSIVE}
  134. index := FindOption(name);
  135. IF (index # Invalid) THEN
  136. IF (options[index].type = Integer) THEN
  137. IF (options[index].isSet) THEN
  138. integer := options[index].value;
  139. RETURN TRUE;
  140. END;
  141. ELSE
  142. WrongUsage(options[index]);
  143. END;
  144. END;
  145. RETURN FALSE;
  146. END GetInteger;
  147. PROCEDURE SetInteger*(ch : CHAR; CONST name : Name; CONST string : ARRAY OF CHAR) : BOOLEAN;
  148. VAR index : LONGINT; optionName : Name;
  149. BEGIN {EXCLUSIVE}
  150. IF (ch = 0X) THEN optionName := name; ELSE optionName[0] := ch; optionName[1] := 0X; END;
  151. index := FindOption(optionName);
  152. IF (index # Invalid) & (options[index].type = Integer) THEN
  153. options[index].timestamp := timestamp;
  154. options[index].isSet := TRUE;
  155. RETURN TRUE;
  156. ELSE
  157. RETURN FALSE;
  158. END;
  159. END SetInteger;
  160. (** Check whether an option of type String is set and retrieve its value if so *)
  161. PROCEDURE GetString*(CONST name : Name; VAR string : ARRAY OF CHAR) : BOOLEAN;
  162. VAR index : LONGINT;
  163. BEGIN {EXCLUSIVE}
  164. index := FindOption(name);
  165. IF (index # Invalid) THEN
  166. IF (options[index].type = String) THEN
  167. IF (options[index].isSet) THEN
  168. COPY(options[index].string, string);
  169. RETURN TRUE;
  170. END;
  171. ELSE
  172. WrongUsage(options[index]);
  173. END;
  174. END;
  175. RETURN FALSE;
  176. END GetString;
  177. PROCEDURE SetString*(ch : CHAR; CONST name : Name; CONST string : ARRAY OF CHAR) : BOOLEAN;
  178. BEGIN {EXCLUSIVE}
  179. RETURN SetStringIntern(ch, name, string, FALSE);
  180. END SetString;
  181. PROCEDURE GetReal*(CONST name : Name; VAR real: LONGREAL) : BOOLEAN;
  182. VAR index: LONGINT;
  183. BEGIN{EXCLUSIVE}
  184. index:=FindOption(name);
  185. IF (index#Invalid) THEN
  186. IF (options[index].type=Real) THEN
  187. IF(options[index].isSet) THEN
  188. real:=options[index].rvalue;
  189. RETURN TRUE;
  190. END;
  191. ELSE
  192. WrongUsage(options[index]);
  193. END;
  194. END;
  195. RETURN FALSE;
  196. END GetReal;
  197. (** Unset all options *)
  198. PROCEDURE Clear*;
  199. VAR i : LONGINT;
  200. BEGIN {EXCLUSIVE}
  201. FOR i := 0 TO nofOptions-1 DO
  202. options[i].isSet := FALSE;
  203. END;
  204. END Clear;
  205. (** Remove all declared options *)
  206. PROCEDURE Reset*;
  207. VAR i : LONGINT;
  208. BEGIN {EXCLUSIVE}
  209. nofOptions := 0; timestamp := 0;
  210. FOR i := 0 TO MaxOptions-1 DO
  211. options[i].isSet := FALSE;
  212. options[i].timestamp := 0;
  213. options[i].ch := 0X;
  214. options[i].name := "";
  215. options[i].type := Unknown;
  216. options[i].value := 0;
  217. options[i].rvalue:=0;
  218. options[i].string := "";
  219. END;
  220. setError := FALSE;
  221. END Reset;
  222. (** Parse options from the argument stream starting at the current position (skip whitespace).
  223. The actual options will be set as side effect when parsing.
  224. *)
  225. PROCEDURE Parse*(arg : Streams.Reader; error : Streams.Writer) : BOOLEAN;
  226. BEGIN
  227. RETURN ParseWithOptions(arg, error, TRUE);
  228. END Parse;
  229. PROCEDURE ParseStaged*(arg : Streams.Reader; error : Streams.Writer) : BOOLEAN;
  230. BEGIN
  231. RETURN ParseWithOptions(arg, error, FALSE);
  232. END ParseStaged;
  233. PROCEDURE ParseWithOptions(arg : Streams.Reader; error : Streams.Writer; flagUnknownOptions : BOOLEAN) : BOOLEAN;
  234. VAR succeeded : BOOLEAN;
  235. BEGIN {EXCLUSIVE}
  236. ASSERT(arg # NIL);
  237. SELF.arg := arg; SELF.error := error;
  238. SELF.flagUnknownOptions := flagUnknownOptions;
  239. INC(timestamp);
  240. arg.SkipWhitespace;
  241. setError := FALSE;
  242. succeeded := ParseOptions() & ~setError;
  243. IF ~succeeded & (error # NIL) THEN error.Update; END;
  244. RETURN succeeded;
  245. END ParseWithOptions;
  246. PROCEDURE ParseString*(CONST string : ARRAY OF CHAR; error : Streams.Writer) : BOOLEAN;
  247. VAR reader : Streams.StringReader;
  248. BEGIN
  249. NEW(reader, LEN(string)); reader.SetRaw(string, 0, LEN(string));
  250. RETURN ParseWithOptions(reader, error, TRUE);
  251. END ParseString;
  252. (* Options = [ "-" Option [ WhiteSpace { "-" Option} ] ] *)
  253. PROCEDURE ParseOptions() : BOOLEAN;
  254. VAR ch : CHAR; oldPos : LONGINT;
  255. BEGIN
  256. oldPos := arg.Pos();
  257. ch := arg.Peek();
  258. WHILE (ch = OptionDelimiter) DO
  259. arg.Char(ch); (* consume OptionDelimiter *)
  260. ch := arg.Peek();
  261. IF ("0" <= ch) & (ch <= "9") THEN
  262. IF arg.CanSetPos() THEN
  263. arg.SetPos(oldPos);
  264. ELSE
  265. KernelLog.String("Options.ParseOptions: Warning: Streams integrity destroyed..."); KernelLog.Ln;
  266. END;
  267. RETURN TRUE;
  268. END;
  269. IF ~ParseOption() THEN
  270. RETURN FALSE;
  271. END;
  272. oldPos := arg.Pos();
  273. arg.SkipWhitespace;
  274. ch := arg.Peek();
  275. END;
  276. RETURN TRUE;
  277. END ParseOptions;
  278. (* Option = "-" NameOption | CharOption *)
  279. PROCEDURE ParseOption() : BOOLEAN;
  280. VAR ch : CHAR;
  281. BEGIN
  282. ch := arg.Peek();
  283. IF (ch = OptionDelimiter) THEN
  284. arg.Char(ch); (* consume OptionDelimiter *)
  285. RETURN ParseNameOption();
  286. ELSIF IsValidChar(ch) THEN
  287. RETURN ParseCharOption();
  288. ELSE
  289. IF (error # NIL) THEN
  290. ShowPos(arg.Pos());
  291. error.String('Expected "'); error.Char(OptionDelimiter);
  292. error.String('" or valid option char'); error.Ln;
  293. END;
  294. RETURN FALSE;
  295. END;
  296. END ParseOption;
  297. (* NameOption = Name [Assignment] *)
  298. PROCEDURE ParseNameOption() : BOOLEAN;
  299. VAR ch : CHAR; name : Name; parameter : Parameter; ignoreHere : BOOLEAN;
  300. BEGIN
  301. IF ParseName(name, 0X) THEN
  302. ch := arg.Peek();
  303. IF (ch = Assignment) THEN
  304. IF ParseAssignment(parameter) THEN
  305. ignoreHere := SetStringIntern(0X, name, parameter, TRUE);
  306. RETURN TRUE;
  307. END;
  308. ELSIF (ch > " ") THEN
  309. IF (error # NIL) THEN
  310. ShowPos(arg.Pos());
  311. error.String("Expected white space"); error.Ln;
  312. END;
  313. ELSE
  314. ignoreHere := SetFlagIntern(0X, name, TRUE);
  315. RETURN TRUE;
  316. END;
  317. END;
  318. RETURN FALSE;
  319. END ParseNameOption;
  320. (* Name = Char Char {Char} *)
  321. PROCEDURE ParseName(VAR name : ARRAY OF CHAR; firstChar : CHAR) : BOOLEAN;
  322. VAR ch : CHAR; i : LONGINT; pos : LONGINT;
  323. BEGIN
  324. pos := arg.Pos();
  325. IF (firstChar # 0X) OR ParseChar(name[0]) THEN
  326. IF ParseChar(name[1]) THEN
  327. i := 2;
  328. ch := arg.Peek();
  329. WHILE (i < LEN(name)-1) & IsValidChar(ch) DO
  330. arg.Char(name[i]); INC(i);
  331. ch := arg.Peek();
  332. END;
  333. name[i] := 0X;
  334. IF (i >= LEN(name)-1) & IsValidChar(ch) THEN
  335. IF (error # NIL) THEN ShowPos(pos); error.String(": Name is too long"); error.Ln; END;
  336. ELSE
  337. RETURN TRUE;
  338. END;
  339. END;
  340. END;
  341. RETURN FALSE;
  342. END ParseName;
  343. (* CharOption = Char [Assignment] | Flags *)
  344. PROCEDURE ParseCharOption() : BOOLEAN;
  345. VAR ch, optionChar : CHAR; parameter : Parameter; ignoreHere : BOOLEAN; count : LONGINT;
  346. BEGIN
  347. IF ParseChar(optionChar) THEN
  348. ch := arg.Peek();
  349. IF (ch = Assignment) THEN (* Char [Assignment] *)
  350. IF ParseAssignment(parameter) THEN
  351. ignoreHere := SetStringIntern(optionChar, "", parameter, TRUE);
  352. RETURN TRUE;
  353. ELSE
  354. RETURN FALSE;
  355. END;
  356. END;
  357. ignoreHere := SetFlagIntern(optionChar, "", TRUE);
  358. count := 1;
  359. ch := arg.Peek();
  360. WHILE IsValidChar(ch) & (count <= MaxOptions) DO (* Flags *)
  361. arg.Char(optionChar);
  362. ignoreHere := SetFlagIntern(optionChar, "", TRUE);
  363. INC(count);
  364. ch := arg.Peek();
  365. END;
  366. IF (ch = Assignment) THEN
  367. IF (error # NIL) THEN ShowPos(arg.Pos()); error.String(": Assignment to set of flags not allowed"); error.Ln; END;
  368. ELSIF (ch <= " ") THEN
  369. RETURN TRUE;
  370. ELSIF (count > MaxOptions) THEN
  371. (* SetFlagIntern will report this error *)
  372. ELSE
  373. IF (error # NIL) THEN ShowPos(arg.Pos()); error.String(": Expected option character"); error.Ln; END;
  374. END;
  375. END;
  376. RETURN FALSE;
  377. END ParseCharOption;
  378. (* Assignment = "=" (EnquotedString | Name | Char) *)
  379. PROCEDURE ParseAssignment(VAR parameter : Parameter) : BOOLEAN;
  380. VAR ch : CHAR; delimiter : CHAR; i : LONGINT;
  381. BEGIN
  382. arg.Char(ch);
  383. ASSERT(ch = Assignment);
  384. ch := arg.Peek();
  385. IF (ch = '"') OR (ch = "'") THEN
  386. arg.Char(delimiter);
  387. ch := arg.Peek();
  388. i := 0;
  389. WHILE (i < LEN(parameter)-1) & (ch # delimiter) DO
  390. arg.Char(parameter[i]); INC(i);
  391. ch := arg.Peek();
  392. END;
  393. IF (ch = delimiter) THEN
  394. arg.Char(ch); (* consume delimiter *)
  395. RETURN TRUE;
  396. ELSIF (error #NIL) THEN
  397. ShowPos(arg.Pos());
  398. error.String("Parameter is too long"); error.Ln;
  399. error.Update;
  400. END;
  401. ELSIF IsValidChar(ch) THEN
  402. arg.Char(parameter[0]);
  403. ch := arg.Peek();
  404. IF IsValidChar(ch) THEN (* Name *)
  405. RETURN ParseName(parameter, ch);
  406. ELSE (* Char *)
  407. parameter[1] := 0X;
  408. RETURN TRUE;
  409. END;
  410. ELSIF (error # NIL) THEN
  411. ShowPos(arg.Pos());
  412. error.String("Expected assignment value"); error.Ln;
  413. error.Update;
  414. END;
  415. RETURN FALSE;
  416. END ParseAssignment;
  417. PROCEDURE ParseChar(VAR ch : CHAR) : BOOLEAN;
  418. BEGIN
  419. ch := arg.Peek();
  420. IF IsValidChar(ch) THEN
  421. arg.Char(ch);
  422. RETURN TRUE;
  423. ELSE
  424. IF (error # NIL) THEN
  425. ShowPos(arg.Pos());
  426. error.String("Expected option character"); error.Ln;
  427. error.Update;
  428. END;
  429. RETURN FALSE;
  430. END;
  431. END ParseChar;
  432. PROCEDURE SetFlagIntern(ch : CHAR; CONST name : Name; checkTimestamp : BOOLEAN) : BOOLEAN;
  433. VAR index : LONGINT; optionName : Name;
  434. BEGIN
  435. IF (ch = 0X) THEN optionName := name; ELSE optionName[0] := ch; optionName[1] := 0X; END;
  436. index := FindOption(optionName);
  437. IF (index # Invalid) THEN
  438. IF ~checkTimestamp OR (options[index].timestamp < timestamp) THEN
  439. IF (options[index].type = Flag) THEN
  440. options[index].timestamp := timestamp;
  441. options[index].isSet := TRUE;
  442. RETURN TRUE;
  443. ELSIF (error # NIL) THEN
  444. error.String("Option "); ShowOption(ch, name);
  445. error.String(" requires a parameter"); error.Ln;
  446. END;
  447. ELSIF (error # NIL) THEN
  448. error.String("Option "); ShowOption(ch, name);
  449. error.String(" set multiple times"); error.Ln;
  450. END;
  451. ELSIF (error # NIL) & flagUnknownOptions THEN
  452. error.String("Unknown option "); ShowOption(ch, name); error.Ln;
  453. END;
  454. setError := TRUE;
  455. RETURN FALSE;
  456. END SetFlagIntern;
  457. PROCEDURE SetStringIntern(ch : CHAR; CONST name : Name; CONST string : ARRAY OF CHAR; checkTimestamp : BOOLEAN) : BOOLEAN;
  458. VAR index : LONGINT; optionName : Name;
  459. BEGIN
  460. IF (ch = 0X) THEN optionName := name; ELSE optionName[0] := ch; optionName[1] := 0X; END;
  461. index := FindOption(optionName);
  462. IF (index # Invalid) THEN
  463. IF ~checkTimestamp OR (options[index].timestamp < timestamp) THEN
  464. options[index].timestamp := timestamp;
  465. CASE options[index].type OF
  466. | Flag:
  467. IF (string = "yes") OR (string = "on") OR (string = "true") THEN
  468. options[index].isSet := TRUE;
  469. RETURN TRUE;
  470. ELSIF (string = "no") OR (string = "off") OR (string = "false") THEN
  471. options[index].isSet := FALSE;
  472. RETURN TRUE;
  473. ELSIF (error # NIL) THEN
  474. error.String("Option "); ShowOption(ch, name);
  475. error.String(" expects a boolean parameter"); error.Ln;
  476. END;
  477. | String:
  478. options[index].isSet := TRUE;
  479. COPY(string, options[index].string);
  480. RETURN TRUE;
  481. | Integer:
  482. IF StringToInteger(string, options[index].value, TRUE) THEN
  483. options[index].isSet := TRUE;
  484. RETURN TRUE;
  485. ELSIF (error # NIL) THEN
  486. error.String("Option "); ShowOption(ch, name);
  487. error.String(" expects decimal number as parameter"); error.Ln;
  488. END;
  489. | Real:
  490. Strings.StrToFloat(string, options[index].rvalue);
  491. options[index].isSet:=TRUE;
  492. RETURN TRUE;
  493. END;
  494. ELSIF (error # NIL) THEN
  495. error.String("Option "); ShowOption(ch, name);
  496. error.String(" set multiple times"); error.Ln;
  497. END;
  498. ELSIF (error # NIL) & flagUnknownOptions THEN
  499. error.String("Unknown option "); ShowOption(ch, name); error.Ln;
  500. END;
  501. setError := TRUE;
  502. RETURN FALSE;
  503. END SetStringIntern;
  504. (* Returns the index of option with character 'ch' or name 'name' or Invalid, if option not found *)
  505. PROCEDURE FindOption(CONST name : Name) : LONGINT;
  506. VAR ch : CHAR; i : LONGINT;
  507. BEGIN
  508. IF (name[1] = 0X) THEN ch := name[0]; ELSE ch := 0X; END;
  509. FOR i := 0 TO nofOptions-1 DO
  510. IF ((options[i].ch # 0X) & (options[i].ch = ch)) OR ((options[i].name # "") & (options[i].name = name)) THEN
  511. RETURN i;
  512. END;
  513. END;
  514. RETURN Invalid;
  515. END FindOption;
  516. PROCEDURE WrongUsage(option : Option);
  517. BEGIN
  518. IF (error # NIL) THEN
  519. error.String("Warning: Option declaration does not match option usage.");
  520. error.Ln; error.Update;
  521. END;
  522. END WrongUsage;
  523. PROCEDURE ShowPos(pos : LONGINT);
  524. BEGIN
  525. IF (error # NIL) THEN
  526. error.String("Pos "); error.Int(pos, 2); error.String(": ");
  527. END;
  528. END ShowPos;
  529. PROCEDURE ShowOption(ch : CHAR; CONST name : Name);
  530. BEGIN
  531. IF (ch # 0X) THEN
  532. error.Char("-"); error.Char(ch);
  533. ELSE
  534. error.String("--"); error.String(name);
  535. END;
  536. END ShowOption;
  537. (** Debug: List all known options and their current values *)
  538. PROCEDURE Show*(out : Streams.Writer);
  539. VAR i : LONGINT;
  540. BEGIN {EXCLUSIVE}
  541. IF (nofOptions > 0) THEN
  542. FOR i := 0 TO (nofOptions-1) DO
  543. out.Int(i+1, 2); out.String(": ");
  544. IF (options[i].ch # 0X) THEN
  545. out.Char(options[i].ch);
  546. IF (options[i].name # "") THEN out.String(", "); END;
  547. END;
  548. IF (options[i].name # "") THEN
  549. out.String(options[i].name);
  550. END;
  551. out.String(", Set: ");
  552. IF options[i].isSet THEN out.String("Yes"); ELSE out.String("No"); END;
  553. out.String(", Type: ");
  554. CASE options[i].type OF
  555. |Flag:
  556. out.String("Flag");
  557. |String:
  558. out.String("String");
  559. IF (options[i].isSet) THEN out.String(" ("); out.String(options[i].string); out.String(")"); END;
  560. |Integer:
  561. out.String("Integer");
  562. IF (options[i].isSet) THEN out.String(" ("); out.Int(options[i].value, 0); out.String(")"); END;
  563. ELSE
  564. out.String("Unknown");
  565. END;
  566. out.Ln;
  567. END;
  568. ELSE
  569. out.String("No options set"); out.Ln;
  570. END;
  571. out.Update;
  572. END Show;
  573. END Options;
  574. Default = POINTER TO RECORD
  575. name: Name;
  576. defaults: Strings.String;
  577. next: Default;
  578. END;
  579. Defaults* = OBJECT
  580. VAR
  581. head, tail: Default;
  582. PROCEDURE &InitDefaults*;
  583. BEGIN
  584. head := NIL; tail := NIL;
  585. END InitDefaults;
  586. PROCEDURE Add*(CONST name, defaults: ARRAY OF CHAR);
  587. VAR default: Default;
  588. BEGIN
  589. default := head;
  590. WHILE (default # NIL) & (default.name # name) DO
  591. default := default.next;
  592. END;
  593. IF default = NIL THEN
  594. NEW(default);
  595. COPY(name, default.name); default.defaults := Strings.NewString(defaults);
  596. IF tail = NIL THEN head := default
  597. ELSE tail.next := default;
  598. END;
  599. tail := default;
  600. ELSE
  601. default.defaults := Strings.ConcatToNew(default.defaults^, " ");
  602. default.defaults := Strings.ConcatToNew(default.defaults^, defaults);
  603. END;
  604. END Add;
  605. PROCEDURE Get*(CONST name: ARRAY OF CHAR): Streams.Reader;
  606. VAR s: Streams.StringReader; default: Default;
  607. BEGIN
  608. s := NIL;
  609. default := head;
  610. WHILE (default # NIL) & (default.name # name) DO
  611. default := default.next;
  612. END;
  613. IF default # NIL THEN
  614. NEW(s, LEN(default.defaults));
  615. s.Set(default.defaults^);
  616. END;
  617. RETURN s;
  618. END Get;
  619. PROCEDURE Show*(out: Streams.Writer);
  620. VAR default: Default;
  621. BEGIN
  622. default := head;
  623. WHILE (default # NIL) DO
  624. out.String(default.name); out.String(" : "); out.String(default.defaults^); out.Ln;
  625. default := default.next;
  626. END;
  627. END Show;
  628. PROCEDURE Find*(CONST option, value: ARRAY OF CHAR; VAR name: ARRAY OF CHAR);
  629. VAR default: Default; pattern: Name; pos, i: LONGINT;
  630. BEGIN
  631. default := head; pattern := "--";
  632. Strings.Concat(pattern, option, pattern);
  633. Strings.Concat(pattern, "=", pattern);
  634. WHILE (default # NIL) DO
  635. pos := Strings.Pos(pattern, default.defaults^);
  636. IF pos # -1 THEN
  637. pos := Strings.Find(default.defaults^, pos, '=') + 1; i := 0;
  638. WHILE (default.defaults[pos] # 0X) & (value[i] # 0X) & (default.defaults[pos] = value[i]) DO INC(pos); INC(i) END;
  639. IF ((default.defaults[pos] = 0X) OR (default.defaults[pos] = ' ')) & (value[i] = 0X) THEN COPY(default.name, name); RETURN END;
  640. END;
  641. default := default.next;
  642. END;
  643. name := "";
  644. END Find;
  645. END Defaults;
  646. (** merge base options into options -- replacing only options that had not been set previously *)
  647. PROCEDURE Merge*(VAR this: Options; base: Options);
  648. VAR i,index: LONGINT;
  649. BEGIN
  650. IF (base # NIL) THEN
  651. IF this = NIL THEN
  652. this := base
  653. ELSE
  654. FOR i := 0 TO base.nofOptions-1 DO
  655. IF (base.options[i].isSet) THEN
  656. index := this.FindOption(base.options[i].name);
  657. IF ~this.options[i].isSet THEN
  658. this.options[index] := base.options[i]
  659. END
  660. END;
  661. END;
  662. END
  663. END;
  664. END Merge;
  665. PROCEDURE IsValidChar(ch : CHAR) : BOOLEAN;
  666. BEGIN
  667. RETURN (" " < ch) & (ch < CHR(128)) & (ch # OptionDelimiter) & (ch # Assignment);
  668. END IsValidChar;
  669. PROCEDURE StringToInteger*(CONST string : ARRAY OF CHAR; VAR x: LONGINT; hex: BOOLEAN) : BOOLEAN;
  670. VAR pos, vd, vh, sign, d: LONGINT; ch: CHAR; ok: BOOLEAN;
  671. BEGIN
  672. IF (LEN(string) <= 0) THEN RETURN FALSE; END;
  673. pos := 0;
  674. IF (string[pos] = "-") THEN sign := -1; INC(pos);
  675. ELSIF (string[pos] = "+") THEN sign := 1; INC(pos);
  676. ELSE sign := 1;
  677. END;
  678. vd := 0; vh := 0; ok := FALSE; d := 0;
  679. LOOP
  680. IF (pos >= LEN(string)) THEN EXIT; END;
  681. ch := string[pos];
  682. IF (ch >= "0") & (ch <= "9") THEN
  683. d := ORD( ch ) - ORD( "0" );
  684. ELSIF hex & (CAP( ch ) >= "A") & (CAP( ch ) <= "F") THEN
  685. d := ORD( CAP( ch ) ) - ORD( "A" ) + 10;
  686. ELSE
  687. EXIT;
  688. END;
  689. vd := 10 * vd + d; vh := 16 * vh + d; (* ignore overflow *)
  690. INC(pos); ok := TRUE
  691. END;
  692. IF hex & (CAP( ch ) = "H") THEN (* optional "H" present *)
  693. vd := vh; (* use the hex value *)
  694. INC(pos);
  695. END;
  696. x := sign * vd;
  697. RETURN ok & ((pos >= LEN(string)) OR (string[pos] <= " "));
  698. END StringToInteger;
  699. PROCEDURE Test*(context : Commands.Context);
  700. VAR options : Options; string : ARRAY 32 OF CHAR;
  701. BEGIN
  702. NEW(options);
  703. options.Add("f", "flag", Flag);
  704. options.Add("s", "string", String);
  705. options.Add("i", "integer", Integer);
  706. IF options.Parse(context.arg, context.error) THEN
  707. context.out.Ln;
  708. options.Show(context.out);
  709. context.arg.SkipWhitespace;
  710. context.arg.String(string);
  711. IF options.GetFlag("dw") THEN END;
  712. context.out.String("Parsed argument: "); context.out.String(string);
  713. context.out.Ln; context.out.Update;
  714. IF options.Parse(context.arg, context.error) THEN
  715. options.Show(context.out);
  716. context.out.Ln;
  717. END;
  718. context.out.String("Parsing a string..");
  719. context.out.Ln; context.out.Update;
  720. options.Clear;
  721. IF options.ParseString("--flag -s=Hello -i=99 ", context.error) THEN
  722. options.Show(context.out);
  723. END;
  724. ELSE
  725. context.result := Commands.CommandParseError;
  726. END;
  727. END Test;
  728. END Options.
  729. Options.Test --string="Hello World" -i=3432 --flag "This is the first argument" --string="Override string option" ~
  730. Options.Test -i="99" --flag ~
  731. Options.Test -afds -b --fdas ~
  732. Options.Test -f -s=fds ~
  733. Options.Test -f=true ~
  734. Options.Test --flag=no ~
  735. System.Free Options ~