Options.Mod 22 KB

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