git @ Cat's Eye Technologies pibfi / master src / pibfi_options.erl
master

Tree @master (Download .tar.gz)

pibfi_options.erl @masterraw · history · blame

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
%%% BEGIN pibfi_options.erl %%%
%%%
%%% pibfi - Platonic Ideal Brainf*ck Interpreter
%%% Copyright (c)2003 Cat's Eye Technologies.  All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without
%%% modification, are permitted provided that the following conditions
%%% are met:
%%%
%%%   Redistributions of source code must retain the above copyright
%%%   notice, this list of conditions and the following disclaimer.
%%%
%%%   Redistributions in binary form must reproduce the above copyright
%%%   notice, this list of conditions and the following disclaimer in
%%%   the documentation and/or other materials provided with the
%%%   distribution.
%%%
%%%   Neither the name of Cat's Eye Technologies nor the names of its
%%%   contributors may be used to endorse or promote products derived
%%%   from this software without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%% CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
%%% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
%%% OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
%%% PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
%%% OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
%%% OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
%%% OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
%%% POSSIBILITY OF SUCH DAMAGE. 

%% @doc Options parser for <code>pibfi</code>.
%%
%% <h3>Synopsis</h3>
%%
%% <p>This module implements collecting options
%% from the command line.</p>
%%
%% <p>When invoking <code>pibfi</code> from the command line, the
%% following syntax can be used:</p>
%%
%% <ul><code>erl -noshell <i>options</i> -run pibfi run <i>filename</i></code></ul>
%%
%% <p>The following options are recognized.</p>
%%
%% <ul>
%% <li>Options pertaining to the parser:
%%
%% <ul>
%% <li><code>-dontstrip <i>s</i></code>, where <i>s</i> is a string of
%% characters, default null (empty), specifies the set of characters
%% (beyond the 8 basic Brainf*ck instructions)
%% which
%% will not be stripped from the parsed source prior to optimization.
%% Note that if this option if given without an argument, it is assumed
%% to be the set of all applicable characters - in other words, nothing
%% at all will be stripped.</li>
%%
%% <li><code>-optimize <i>n</i></code>, where <i>n</i> is an integer,
%% default 1, sets the optimization level of the parser.
%% At optimization level 0, no optimization occurs.
%% At optimization level 1, run-length encoding is performed on the
%% instructions.  Further optimization levels may be defined in the
%% future.  Note that if this option if given without an argument,
%% it is assumed to be asking for the most thorough yet quickly
%% available optimization.</li>
%%
%% <li><code>-statuscmd <i>c</i></code>, where <i>c</i> may be
%% any single-character string of the term <code>undefined</code>,
%% default "#" (octalthorpe), specifies
%% which symbol, when encountered, is treated
%% as a debugging instruction which displays a status report.</li>
%%
%% <li><code>-heredoc <i>c</i></code>, where <i>c</i> may be
%% a single-character string or the term <code>undefined</code>,
%% default <code>undefined</code>, specifies which symbol when encountered,
%% is treated as the marker that signifies that input is embedded in
%% the program source code following it.  This input will be stripped
%% from the source (even if it contains valid Brainf*ck instructions)
%% and will be made available to <code>-infile heredoc</code> (see
%% below.)  Note that if this option is given with no argument,
%% "!" (exclamation point) is assumed.</li>
%%
%% </ul>
%% </li>
%% <li>Options pertaining to the Brainf*ck tape:
%%
%% <ul>
%% <li><code>-tapemodule <i>m</i></code>, where <i>n</i> is the name of
%% an Erlang module conforming to the <code>pibfi_tape</code> behaviour,
%% defaulting to whatever <code>pibfi</code> determines would be optimal
%% for the given sourcecode, (currently always <code>pibfi_tape_ets</code>),
%% names the module which implements the backing for the simulated tape.</li>
%%
%% <li><code>-maxcell <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default <code>infinity</code>, sets the
%% highest value that can be placed into a cell on the tape.</li>
%%
%% <li><code>-mincell <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default 0, sets the
%% lowest value that can be placed into a cell on the tape.
%% (Note that <code>infinity</code> actually represents negative infinity
%% here.)</li>
%%
%% <li><code>-wrapcell <i>b</i></code>, where <i>b</i> is a boolean
%% (<code>true</code> or <code>false</code>), default <code>false</code>,
%% determines what happens when either limit of any cell in the tape is
%% exceeded.  When <code>-wrapcell</code> is <code>true</code>, the limits
%% will be taken as modulus boundaries, and the value will 'wrap around'
%% to the opposite limit.  When <code>-wrapcell</code> is <code>false</code>,
%% an exception will be generated.  Note that if <i>b</i> is omitted
%% after <code>-wrapcell</code>, <code>true</code> is assumed.
%% Also note that <code>-wrapcell true</code> is not compatible with
%% either <code>-maxcell infinity</code> or <code>-mincell infinity</code>
%% (for what should be obvious reasons.)</li>
%%
%% <li><code>-maxtape <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default <code>infinity</code>, sets the
%% rightmost position to which the tape head can move.  Note that the initial
%% position of the tape head is considered position 0.</li>
%%
%% <li><code>-mintape <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default 0, sets the
%% leftmost position to which the tape head can move.
%% (Note that <code>infinity</code> actually represents negative infinity
%% here.)</li>
%%
%% <li><code>-wraptape <i>b</i></code>, where <i>b</i> is a boolean,
%% default <code>false</code>, determines what happens when either limit
%% of the tape head is exceeded.  With <code>-wraptape true</code>,
%% the limits will be taken as modulus boundaries,
%% and the position of the tape head will 'wrap around' to the
%% opposite limit.  With <code>-wraptape false</code>,
%% an exception will be generated when this happens.
%% Note that if <i>b</i> is omitted
%% after <code>-wraptape</code>, <code>true</code> is assumed. Also note
%% that <code>-wraptape true</code> is not compatible with either
%% <code>-maxtape infinity</code> or <code>-mintape infinity</code>.</li>
%% </ul>
%% </li>
%%
%% <li>Options pertaining to input and output:
%%
%% <ul>
%% <li><code>-infile <i>s</i></code>, where <i>s</i> is a filename or
%% one of the terms <code>tty</code> or <code>heredoc</code>,
%% default <code>tty</code>, sets the
%% source of the input to the Brainf*ck program.
%% <code>tty</code> indicates an interactive terminal session with
%% "standard input".
%% <code>heredoc</code> indicates input will come from the
%% "here-doc" portion of the program source code (note that the
%% <code>-heredoc</code> option must also be given to parse the
%% source code.)
%% Using this option is preferred
%% over redirecting standard I/O with the shell, as it is a hint to the
%% interpreter that the program is not being run interactively.</li>
%%
%% <li><code>-outfile <i>s</i></code>, where <i>s</i> is a filename or
%% the term <code>tty</code>, default <code>tty</code>, sets the
%% destination of the output of the Brainf*ck program.  This is preferred
%% to redirecting standard I/O with the shell, as it is a hint to the
%% interpreter that the program is not being run interactively.</li>
%%
%% <li><code>-maxout <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default <code>infinity</code>, sets the
%% maximum character value which can be output.</li>
%%
%% <li><code>-minout <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default 0, sets the minimum character
%% value which can be output.
%% (Note that <code>infinity</code> actually represents negative infinity
%% here.)</li>
%%
%% <li><code>-wrapout <i>b</i></code>, where <i>b</i> is a boolean,
%% default <code>false</code>, determines what happens when either limit
%% of character output is exceeded.  With <code>-wrapout true</code>,
%% the limits will be taken as modulus boundaries,
%% and the actual character output will be computed by 'wrapping around'
%% the rquested value to the opposite limit.
%% With <code>-wrapout false</code>,
%% an exception will be generated.  Note that if <i>b</i> is omitted
%% after <code>-wrapout</code>, <code>true</code> is assumed. Also note
%% that <code>-wrapout true</code> is not compatible with either
%% <code>-maxout infinity</code> or <code>-minout infinity</code>.</li>
%%
%% <li><code>-maxin <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default <code>infinity</code>, sets the
%% maximum character value which can be input.</li>
%%
%% <li><code>-minin <i>n</i></code>, where <i>n</i> is any integer or the
%% term <code>infinity</code>, default 0, sets the minimum character
%% value which can be input.
%% (Note that <code>infinity</code> actually represents negative infinity
%% here.)</li>
%%
%% <li><code>-wrapin <i>b</i></code>, where <i>b</i> is a boolean,
%% default <code>false</code>, determines what happens when either limit
%% of character input is exceeded.  With <code>-wrapin true</code>,
%% the limits will be taken as modulus boundaries,
%% and the actual character input will be computed by 'wrapping around'
%% the rquested value to the opposite limit.
%% With <code>-wrapin false</code>,
%% an exception will be generated.  Note that if <i>b</i> is omitted
%% after <code>-wrapin</code>, <code>true</code> is assumed. Also note
%% that <code>-wrapin true</code> is not compatible with either
%% <code>-maxin infinity</code> or <code>-minin infinity</code>.</li>
%%
%% <li><code>-xlatout <i>s</i></code>, where <i>s</i> is string
%% in the form given below,
%% default value <tt>#10=#nl</tt>, specifies a mapping between
%% characters the Brainf*ck program sees itself as sending to output,
%% and the characters that the operating system actually receives.
%% The syntax for the mapping specification is described by this mini-grammar:
%% <ul><li>
%% <tt><i>string</i> "=" <i>string</i> ["," <i>string</i> "=" <i>string</i> ]</tt>
%% </li></ul>
%% Control and other characters can be embedded in this string by
%% giving their ASCII values in decimal preceded by a <tt>#</tt>
%% symbol.  A single <tt>#</tt> symbol can be represented by the
%% sequence <tt>#35</tt>.  A single <tt>=</tt> symbol can be
%% represented by the sequence <tt>#61</tt>.
%% A single <tt>,</tt> symbol can be
%% represented by the sequence <tt>#44</tt>.
%% The current operating system newline
%% sequence can be represented by <tt>#nl</tt>.
%% Note that whitespace is not allowed in this grammar,
%% and must be represented by <tt>#32</tt>, etc.
%% Note that if the <code>-xlatout</code> option is present
%% with no argument, this means that there should be <i>no</i> translation
%% mapping between Brainf*ck output and operating system output.</li>
%%
%% <li><code>-xlatin <i>s</i></code>, where <i>s</i> is a string
%% in the form given above,
%% default value <tt>#nl=#10</tt>, specifies a complementary mapping between
%% characters on the operating system's input and
%% the Brainf*ck program's input.
%% Note that if the <code>-xlatin</code> option is present
%% with no argument, this means that there should be no translation
%% mapping of input.</li>
%%
%% <li><code>-eof <i>i</i></code>, where <i>i</i> is any integer
%% or one of the terms <code>halt</code>, <code>stop</code>,
%% or <code>nop</code>, default 0,
%% determines the single ASCII character that will be given repeatedly to
%% the Brainf*ck program during execution of the <code>,</code> instruction
%% at and after the end of input is encountered.
%% If <code>halt</code> is given for <code>eof</code>, the Brainf*ck
%% program will be halted with an error
%% if it attempts to read past the end of user input.
%% If <code>stop</code> is given for <code>eof</code>, the Brainf*ck
%% program will be terminated normally if it attempts to read past
%% the end of user input.
%% If <code>nop</code> is given for <code>eof</code>, the Brainf*ck
%% program will act as if nothing at all happened if it attempts
%% to read past the end of user input (the tape will not be altered in
%% any way.)</li>
%% </ul>
%% </li>
%%
%% <li>Debugging and other options:</li>
%% <ul>
%% <li><code>-quiet</code>, if given, suppresses all startup
%% output generated by the interpreter.  It does not suppress
%% status reports.</li>
%% <li><code>-autopsy</code>, if given, causes the interpreter to
%% issue a status report after the program terminates normally.</li>
%% <li><code>-statusevery <i>n</i></code>, where <i>n</i> is an
%% integer specifying a duration or the term <code>undefined</code>,
%% default <code>undefined</code>, tells <code>pibfi</code>
%% to generate periodic status reports at the given interval,
%% if it is not <code>undefined</code>.  If the duration is given
%% as a plain integer, units of milliseconds are assumed.  The
%% duration may also be given as an integer followed immediately
%% by <code>s</code>, <code>m</code>, or <code>h</code>, in which
%% case the units of measurement will be taken to be seconds,
%% minutes, or hours, respectively.</li>
%% </ul>
%% </ul>
%%
%% @end

-module(pibfi_options).
-vsn('2003.0505').
-copyright('Copyright (c)2003 Cat`s Eye Technologies. All rights reserved.').

-export([get_flag/1, get_opts/1]).
-export([get_option/3]).

%% @spec get_flag(Switch::atom()) -> true | false
%% @doc Gets a switch from the command line.

get_flag(Option) ->
  case init:get_argument(Option) of
    {ok, [X]} ->
      true;
    error ->
      false
  end.

%% @spec get_opts([atom()]) -> [{atom(), term()}]
%% @doc Gets a set of options from the command line.

get_opts(Options) ->
  lists:foldl(fun(Option, Acc) when is_atom(Option) ->
    case init:get_argument(Option) of
      error ->
        Acc;
      {ok, [[]]} ->
        [{Option, missing(Option)} | Acc];
      {ok, [[Arg | _] | _]} ->
        [{Option, convert(Option, Arg)} | Acc]
    end;
    ({Option, Default}, Acc) ->
    case init:get_argument(Option) of
      error ->
        [{Option, Default} | Acc];
      {ok, [[]]} ->
        [{Option, missing(Option)} | Acc];
      {ok, [[Arg | _] | _]} ->
        [{Option, convert(Option, Arg)} | Acc]
    end
  end, [], Options).

%% @spec missing(atom()) -> term()
%% @doc Gets the default value for an option specified with no value
%% on the command line.

missing(wrapcell) -> true;
missing(wraptape) -> true;
missing(wrapout)  -> true;
missing(wrapin)   -> true;
missing(xlatin)   -> [];
missing(xlatout)  -> [];
missing(dontstrip) -> lists:seq(0, 255);
missing(optimize) -> 1;
missing(statuscmd) -> "#";
missing(heredoc)  -> "!";
missing(Else)     -> exit({missing_parameter_for, Else}).

%% @spec convert(atom(), string()) -> term()
%% @doc Converts an option value specified on the command line to a
%% term that <code>pibfi</code> can work with internally.

convert(dontstrip, String)       -> unescape(String);
convert(optimize, Integer)       -> list_to_integer(Integer);
convert(statuscmd, "undefined")  -> undefined;
convert(statuscmd, String)       -> unescape(String);
convert(heredoc, "undefined")    -> undefined;
convert(heredoc, String)         -> unescape(String);

convert(tapemodule, String)  -> list_to_atom(String);
convert(maxcell, "infinity") -> infinity;
convert(maxcell, Else)       -> list_to_integer(Else);
convert(mincell, "infinity") -> infinity;
convert(mincell, Else)       -> list_to_integer(Else);
convert(wrapcell, "true")    -> true;
convert(wrapcell, "false")   -> false;
convert(maxtape, "infinity") -> infinity;
convert(maxtape, Else)       -> list_to_integer(Else);
convert(mintape, "infinity") -> infinity;
convert(mintape, Else)       -> list_to_integer(Else);
convert(wraptape, "true")    -> true;
convert(wraptape, "false")   -> false;

convert(infile, "tty")       -> tty;
convert(infile, "heredoc")   -> heredoc;
convert(infile, Else)        -> Else;
convert(outfile, "tty")      -> tty;
convert(outfile, Else)       -> Else;
convert(maxout, "infinity")  -> infinity;
convert(maxout, Else)        -> list_to_integer(Else);
convert(minout, "infinity")  -> infinity;
convert(minout, Else)        -> list_to_integer(Else);
convert(wrapout, "true")     -> true;
convert(wrapout, "false")    -> false;
convert(maxin, "infinity")   -> infinity;
convert(maxin, Else)         -> list_to_integer(Else);
convert(minin, "infinity")   -> infinity;
convert(minin, Else)         -> list_to_integer(Else);
convert(wrapin, "true")      -> true;
convert(wrapin, "false")     -> false;
convert(xlatin, String)      -> parse_xlat(String, []);
convert(xlatout, String)     -> parse_xlat(String, []);
convert(eof, "halt")         -> halt;
convert(eof, "nop")          -> nop;
convert(eof, "stop")         -> stop;
convert(eof, Else)           -> list_to_integer(Else);

convert(statusevery, "undefined") -> undefined;
convert(statusevery, Else) ->
  Factor = case lists:last(Else) of
    $s -> 1000;
    $m -> 60*1000;
    $h -> 60*60*1000;
    _ -> 1
  end,
  Root = case Factor of
    1 -> Else;
    _ -> lists:reverse(tl(lists:reverse(Else)))
  end,
  % ce_log:write("~p ~p", [Root, Factor]),
  list_to_integer(Root) * Factor.

parse_xlat(String, Acc) ->
  {First,  String0} = parse_lstring(String, ""),
  {Second, String1} = parse_rstring(String0, ""),
  pibfi:assert(First =/= "", {lefthand_string_in_xlat_may_not_be_null, String}),
  Acc0 = [{unescape(First), unescape(Second)} | Acc],
  case String1 of
    "," ++ String2 ->
      parse_xlat(String2, Acc0);
    _ ->
      Acc0
  end.
  
parse_lstring("=" ++ Tail, Acc) ->
  {lists:reverse(Acc), Tail};
parse_lstring([Head | Tail], Acc) ->
  parse_lstring(Tail, [Head | Acc]).

parse_rstring("", Acc) ->
  {lists:reverse(Acc), ""};
parse_rstring("," ++ Tail, Acc) ->
  {lists:reverse(Acc), "," ++ Tail};
parse_rstring([Head | Tail], Acc) ->
  parse_rstring(Tail, [Head | Acc]).

%% @spec unescape(string()) -> string()
%% @doc Transforms escape codes in a string into embedded characters.
%% The escape code <tt>#<i>i</i></tt> where <tt><i>i</i></tt> is a
%% decimal integer of from one to three digits is converted into an
%% ASCII character of that value.

unescape(String) ->
  unescape(String, []).
unescape([], Acc) ->
  lists:reverse(Acc);
unescape([$#, $n, $l | Tail], Acc) ->
 unescape(Tail, lists:reverse(pibfi:os_eol()) ++ Acc);
unescape([$#, D1, D2, D3 | Tail], Acc)
 when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9, D3 >= $0, D3 =< $9 ->
  unescape(Tail, [(D1 - $0) * 100 + (D2 - $0) * 10 + (D3 - $0) | Acc]);
unescape([$#, D1, D2 | Tail], Acc)
 when D1 >= $0, D1 =< $9, D2 >= $0, D2 =< $9 ->
  unescape(Tail, [(D1 - $0) * 10 + (D2 - $0) | Acc]);
unescape([$#, D | Tail], Acc)
 when D >= $0, D =< $9 ->
  unescape(Tail, [(D - $0) | Acc]);
unescape([Head | Tail], Acc) ->
  unescape(Tail, [Head | Acc]).

%% @spec get_option(Options::[{atom(), term()}], Option::atom(), Default::term()) -> term()
%% @doc Gets an option from a list of (already parsed) option tuples.

get_option(TupleList, Option, Default) ->
  case lists:keysearch(Option, 1, TupleList) of
    {value, {Option, Value}} ->
      Value;
    _ ->
      Default
  end.

%%% END of pibfi_options.erl %%%