[m-rev.] for review: fix problems with the char module

Peter Wang novalazy at gmail.com
Thu Sep 11 13:31:22 AEST 2014


On Wed, 10 Sep 2014 17:36:38 +1000 (EST), Julien Fischer <jfischer at opturion.com> wrote:
> 
> For review by anyone.
> 
> ----------------------
> 
> Fix problems with the char module.
> 
> (1) The behaviour of digit_to_int/2 was inconsistent with that of is_digit/2.
> The former succeeds for all of 0-9, a-z and A-Z while the latter succeeds only
> for 0-9 (i.e. it was possible for digit_to_int/2 to succeed for non-decimal
> characters, which is not what was intended in many of it uses).
> 
> (2) Predicates involving hexadecimal digits were inconsistently named, they
> were "hex digits" in one predicate name, "hex chars" in another.
> 
> This change ensures that the following operations are supported for binary,
> octal, decimal and hexadecimal digits and that we use a consistent naming
> scheme for the predicates that implement them:
>      - testing if a character is a digit of the given base
>      - conversion to an int
>      - conversion from an int
> 
> In addition, we also add predicates for supporting these operations for user
> defined bases, ranging from 2-36.
> 
> library/char.m:
>      Add the predicate is_decimal_digit/1, which is a synonym for is_digit/1.
> 
>      Add the predicate is_base_digit/2.
> 
>      Add the predicates int_to_{binary,octal,decimal,hex}_digit/2 and
>      base_int_to_digit/3.
> 
>      Add the predicates {binary,octal,decimal,hex}_digit_to_int/2 and
>      base_digit_to_int/3.
> 
>      Add det function versions of the above.
> 
>      Delete the function det_digit_to_int/1 that I added the other day.
> 
>      Mark the following as obsolete:
>          - is_hex_digit/2
>          - int_to_hex_char/2
>          - int_to_digit/2
>          - det_int_to_digit/1
>          - det_int_to_digit/2

It's probably the right thing to do, but the is_hex_digit and
int_to_hex_char deprecations will be annoying :(

> -* We have added the function det_digit_to_int/1 to the char module.
> +* The following predicates and functions have been added to the char module:
> +
> +    - is_decimal_digit/1
> +    - is_base_digit/2
> +    - int_to_binary_digit/2
> +    - int_to_octal_digit/2
> +    - int_to_decimal_digit/2
> +    - int_to_hex_digit/2
> +    - base_int_to_digit/3, det_base_int_to_digit/2
> +    - binary_digit_to_int/2, det_binary_digit_to_int/1
> +    - octal_digit_to_int/2, det_octal_digit_to_int/1
> +    - decimal_digit_to_int/2, det_decimal_digit_to_int/1
> +    - hex_digit_to_int/2, det_hex_digit_to_int/1
> +    - base_digit_to_int/3, det_base_digit_to_int/2
> +
> +  The following predicates in the char module have been deprecated and will
> +  either be removed or have their semantics changed in a future release.
> +
> +    - is_hex_digit/2
> +    - int_to_hex_char/2
> +    - digit_to_int/2
> +    - int_to_digit/2
> +    - det_int_to_digit/1, det_int_to_digit/2
> +
> +  NOTE: existing code that calls the predicate char.digit_to_int/2 and assumes
> +  that the call will succeed only for those characters for which
> +  char.is_digit/1 is true may be broken.  char.digit_to_int/2 succeeds for
> +  the characters 0-9, a-z and A-Z, not just 0-9.

I suggest:

    NOTE: existing code that calls char.digit_to_int/2 assuming that it
    will only succeed for decimal digits (0-9) may be broken.

> @@ -141,39 +141,125 @@
>       %
>   :- pred char.is_binary_digit(char::in) is semidet.
> 
> -    % True iff the character is a octal digit (0-7) in the ASCII range.
> +    % True iff the character is an octal digit (0-7) in the ASCII range.
>       %
>   :- pred char.is_octal_digit(char::in) is semidet.
> 
> -    % True iff the character is a hexadecimal digit (0-9, a-f, A-F)
> -    % in the ASCII range.
> +    % True iff the character is a decimal digit (0-9) in the ASCII range.
> +    % Synonym for char.is_digit/1.
> +    %
> +:- pred char.is_decimal_digit(char::in) is semidet.
> +
> +    % True iff the character is a hexadecimal digit (0-9, a-f, A-F) in the
> +    % ASCII range.
>       %
>   :- pred char.is_hex_digit(char::in) is semidet.
> + 
> +    % is_base_digit(Base, Digit):
> +    % True iff Digit is a digit in the given Base (0-9, a-z, A-Z).
> +    % Throws an exception if Base < 2 or Base > 36.
> +    %
> +:- pred char.is_base_digit(int::in, char::in) is semidet.
> + 
> +    % Convert an integer in the range 0-1 to a binary digit (0 or 1) in the
> +    % ASCII range.
> +    %
> +:- pred char.int_to_binary_digit(int::in, char::out) is semidet.
> +
> +    % Convert an integer 0-7 to an octal digit (0-7) in the ASCII range.
> +    %
> +:- pred char.int_to_octal_digit(int::in, char::out) is semidet.
> +
> +    % Convert an integer 0-9 to a decimal digit (0-9) in the ASCII range.
> +    %
> +:- pred char.int_to_decimal_digit(int::in, char::out) is semidet.
> +
> +    % Convert an integer 0-15 to a hexadecimal digit (0-9, A-F) in the ASCII
> +    % range.
> +    %
> +:- pred char.int_to_hex_digit(int::in, char::out) is semidet.

to an uppercase hexadecimal digit

> +    % base_int_to_digit(Base, Int, DigitChar):
> +    % True iff `DigitChar' is a decimal digit (0-9) or an uppercase letter
> +    % (A-Z) representing the value `Int' (0-35) in the given base.
> +    % Throws an exception if `Base' < 2 or `Base' > 36.
> +    %
> +:- pred char.base_int_to_digit(int::in, int::in, char::out) is semidet.
> +
> +    % As above, but throw an exception instead of failing.
> +    %
> +:- func char.det_base_int_to_digit(int, int) = char.

> +    % True iff char is a binary digit (0 or 1).
> +    % Returns the character's value as a digit (0-1).
> +    %
> +:- pred char.binary_digit_to_int(char::in, int::out) is semidet.

I suggest:

    % binary_digit_to_int(Char, Int):
    % True iff Char is a binary digit (0-1) representing the value Int.

Similarly for the rest.

> +
> +    % As above, but throws an exception instead of failing.
> +    %
> +:- func char.det_binary_digit_to_int(char) = int.
> +
> +    % True iff char is an octal digit (0-7).
> +    % Returns the character's value as a digit (0-7).
> +    %
> +:- pred char.octal_digit_to_int(char::in, int::out) is semidet.
> +
> +    % As above, but throws an exception instead of failing.
> +    %
> +:- func char.det_octal_digit_to_int(char) = int.
> +
> +    % True iff char is a decimal digit (0-9).
> +    % Returns the character's value as a digit (0-9).
> +    %
> +:- pred char.decimal_digit_to_int(char::in, int::out) is semidet.
> +
> +    % As above, but throws an exception instead of failing.
> +    %
> +:- func char.det_decimal_digit_to_int(char) = int.
> +
> +    % True iff char is a hexadecimal digit (0-9, a-z or A-F).
> +    % Returns the character's value as a digit (0-15).
> +    %
> +:- pred char.hex_digit_to_int(char::in, int::out) is semidet.
> +
> +    % As above, but throws an exception instead of failing.
> +    %
> +:- func char.det_hex_digit_to_int(char) = int.
> +
> +    % base_digit_to_int(Base, DigitChar, Int):
> +    % True iff `DigitChar' is a decimal digit (0-9) or a letter (a-z, A-Z)
> +    % representing the value `Int' (0-35) in the given base.
> +    % Throws an exception if `Base' < 2 or `Base' > 36.
> +    %
> +:- pred char.base_digit_to_int(int::in, char::in, int::out) is semidet.
> +
> +    % As above, but throws an exception instead of failing.
> +    %
> +:- func char.det_base_digit_to_int(int, char) = int.
> +
> +:- pragma obsolete(char.is_hex_digit/2).
>   :- pred char.is_hex_digit(char, int).
>   :- mode char.is_hex_digit(in, out) is semidet.

Please name its replacement in a comment.  Similarly for the rest.

> 
> -    % Convert an integer 0-15 to a hexadecimal digit (0-9, A-F)
> -    % in the ASCII range.
> +    % Convert an integer 0-15 to a hexadecimal digit (0-9, A-F) in the ASCII
> +    % range.
>       %
> +:- pragma obsolete(char.int_to_hex_char/2).
>   :- pred char.int_to_hex_char(int, char).
>   :- mode char.int_to_hex_char(in, out) is semidet.
> 
>       % Succeeds if char is a decimal digit (0-9) or letter (a-z or A-Z).
>       % Returns the character's value as a digit (0-9 or 10-35).
>       %
> +:- pragma obsolete(char.digit_to_int/2).
>   :- pred char.digit_to_int(char::in, int::out) is semidet.
> 
> -    % As above, but calls error/1 instead of failing.
> +    % char.int_to_digit(Int, DigitChar):
>       %
> -:- func char.det_digit_to_int(char) = int.
> -
> -    % char.int_to_uppercase_digit(Int, DigitChar):
> -    %
> -    % True iff `Int' is an integer in the range 0-35 and
> -    % `DigitChar' is a decimal digit or uppercase letter
> -    % whose value as a digit is `Int'.
> +    % True iff `Int' is an integer in the range 0-35 and `DigitChar' is a
> +    % decimal digit or uppercase letter whose value as a digit is `Int'.
>       %
> +:- pragma obsolete(char.int_to_digit/2).
>   :- pred char.int_to_digit(int, char).
>   :- mode char.int_to_digit(in, out) is semidet.
>   :- mode char.int_to_digit(out, in) is semidet.
> @@ -181,7 +267,9 @@
>       % Returns a decimal digit or uppercase letter corresponding to the value.
>       % Calls error/1 if the integer is not in the range 0-35.
>       %
> +:- pragma obsolete(char.det_int_to_digit/1).
>   :- func char.det_int_to_digit(int) = char.
> +:- pragma obsolete(char.det_int_to_digit/2).
>   :- pred char.det_int_to_digit(int::in, char::out) is det.
> 
>       % Convert a char to a pretty_printer.doc for formatting.

> @@ -372,71 +460,228 @@ char.to_upper(Char, Upper) :-
>   % but these versions are very portable.
> 
>   %-----------------------------------------------------------------------------%
> +%
> +% Digit classification.
> +%
> +
> +is_binary_digit('0').
> +is_binary_digit('1').

(Ideally is_*_digit would just call *_digit_to_int and end up with the
same code)

> +is_base_digit(Base, Digit) :-
> +    ( ( Base < 2 ; Base > 36) ->
> +        error("char.is_base_digit: invalid base")
> +    ;
> +        base_digit_to_int(Base, Digit, _Int)
> +    ).

Invert the condition (and elsewhere).

Peter



More information about the reviews mailing list