mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-16 09:53:36 +00:00
Extend the operations that perform formatted conversion, such as string.format/2, to be able to handle values of type uint directly. We have always supported formatting values of type int as unsigned values, but currently the only way to format uint values is by explicitly casting them to an int. This addresses Mantis issue #502. library/string.m: Add a new alternative to the poly_type/0 type that wraps uint values. Update the documentation for string.format. uint values may now be formatted using the u, x, X, o or p conversion specifiers. library/string.format.m: Add the necessary machinery for handling formatting of uint values. library/string.parse_runtime.m: library/string.parse_util.m: Handle uint poly_types. library/io.m:a Handle uint values in the write_many predicates. library/pprint.m: Handle uint values in the poly/1 function. compiler/format_call.m: compiler/parse_string_format.m: Conform to the above changes. compiler/options.m: Add a way to detect if a compiler supports this change. NEWS: Announce the above changes. tests/hard_coded/stream_format.{m,exp}: Extend this test to cover uints. tests/invalid/string_format_bad.m: tests/invalid/string_format_unknown.m: Conform to the above changes. tests/string_format/Mmakefile: tests/string_format/string_format_uint_o.{m,exp,exp2}: tests/string_format/string_format_uint_u.{m,exp,exp2}: tests/string_format/string_format_uint_x.{m,exp,exp2}: Add tests of string.format with uints.
417 lines
15 KiB
Mathematica
417 lines
15 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ts=4 sw=4 et ft=mercury
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2014-2015, 2018-2020 The Mercury team.
|
|
% This file is distributed under the terms specified in COPYING.LIB.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: string.parse_runtime.m.
|
|
%
|
|
% This module parses format strings for the runtime system;
|
|
% the module compiler/parse_string_format.m does the same job for the compiler.
|
|
% Any changes here will probably also require a corresponding change there.
|
|
%
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- module string.parse_runtime.
|
|
:- interface.
|
|
|
|
:- import_module string.parse_util.
|
|
:- import_module list.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- type string_format_spec
|
|
---> spec_constant_string(
|
|
string
|
|
)
|
|
; spec_char(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
char
|
|
)
|
|
; spec_string(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string
|
|
)
|
|
; spec_signed_int(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
int
|
|
)
|
|
; spec_unsigned_int(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_int_base,
|
|
int
|
|
)
|
|
; spec_uint(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_int_base,
|
|
uint
|
|
)
|
|
; spec_float(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_float_kind,
|
|
float
|
|
).
|
|
|
|
% This predicate parses the entire format string. When it encounters
|
|
% something that looks like a conversion specification (i.e. it starts
|
|
% with a '%' character), but which cannot be parsed as one, it records
|
|
% an error message, and keeps going.
|
|
%
|
|
% Note that making this predicate use an accumulator for the lists
|
|
% of specs and errors seen so far would yield cleaner code,
|
|
% but would probably be slower since our caller would have to unreverse
|
|
% the list of specs we return.
|
|
%
|
|
% The lack of tail recursion here should not be a problem, since no
|
|
% format string will be long enough to make us consume too much stack.
|
|
%
|
|
:- pred parse_format_string(list(char)::in,
|
|
list(string.poly_type)::in, int::in,
|
|
list(string_format_spec)::out, list(string_format_error)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module int.
|
|
:- import_module require.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
parse_format_string(!.Chars, !.PolyTypes, SpecNum, Specs, Errors) :-
|
|
gather_non_percent_chars(!.Chars, NonConversionSpecChars, GatherEndedBy),
|
|
(
|
|
GatherEndedBy = found_end_of_string,
|
|
Specs0 = [],
|
|
(
|
|
!.PolyTypes = [],
|
|
Errors = []
|
|
;
|
|
!.PolyTypes = [_ | _],
|
|
Error = error_extra_polytypes(SpecNum, list.length(!.PolyTypes)),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
GatherEndedBy = found_percent(!:Chars),
|
|
parse_conversion_specification(!Chars, !PolyTypes, SpecNum,
|
|
HeadSpec, HeadErrors),
|
|
parse_format_string(!.Chars, !.PolyTypes, SpecNum + 1,
|
|
TailSpecs, TailErrors),
|
|
(
|
|
HeadErrors = [],
|
|
Specs0 = [HeadSpec | TailSpecs],
|
|
Errors = TailErrors
|
|
;
|
|
HeadErrors = [_ | _],
|
|
Specs0 = TailSpecs,
|
|
Errors = HeadErrors ++ TailErrors
|
|
)
|
|
),
|
|
(
|
|
NonConversionSpecChars = [],
|
|
Specs = Specs0
|
|
;
|
|
NonConversionSpecChars = [_ | _],
|
|
NonConversionSpecString =
|
|
string.from_char_list(NonConversionSpecChars),
|
|
StringSpec = spec_constant_string(NonConversionSpecString),
|
|
Specs = [StringSpec | Specs0]
|
|
).
|
|
|
|
% Each conversion specification starts with the character '%' (which
|
|
% our caller has already read) and ends with a conversion specifier.
|
|
% In between there may be (in this order) zero or more flags, an optional
|
|
% minimum field width, and an optional precision.
|
|
%
|
|
:- pred parse_conversion_specification(list(char)::in, list(char)::out,
|
|
list(string.poly_type)::in, list(string.poly_type)::out, int::in,
|
|
string_format_spec::out, list(string_format_error)::out) is det.
|
|
|
|
parse_conversion_specification(!Chars, !PolyTypes, SpecNum, Spec, Errors) :-
|
|
Flags0 = string_format_flags(flag_hash_clear, flag_space_clear,
|
|
flag_zero_clear, flag_minus_clear, flag_plus_clear),
|
|
gather_flag_chars(!Chars, Flags0, Flags),
|
|
get_optional_width(!Chars, !PolyTypes, SpecNum, MaybeWidth, WidthErrors),
|
|
get_optional_prec(!Chars, !PolyTypes, SpecNum, MaybePrec, PrecErrors),
|
|
get_first_spec(!Chars, !PolyTypes, Flags, MaybeWidth, MaybePrec, SpecNum,
|
|
Spec, SpecErrors),
|
|
Errors = WidthErrors ++ PrecErrors ++ SpecErrors.
|
|
|
|
% Do we have a minimum field width? If yes, get it.
|
|
%
|
|
:- pred get_optional_width(list(char)::in, list(char)::out,
|
|
list(string.poly_type)::in, list(string.poly_type)::out, int::in,
|
|
string_format_maybe_width::out, list(string_format_error)::out) is det.
|
|
|
|
get_optional_width(!Chars, !PolyTypes, SpecNum, MaybeWidth, Errors) :-
|
|
( if !.Chars = ['*' | !:Chars] then
|
|
(
|
|
!.PolyTypes = [PolyType | !:PolyTypes],
|
|
( if PolyType = i(PolyWidth) then
|
|
MaybeWidth = specified_width(PolyWidth),
|
|
Errors = []
|
|
else
|
|
MaybeWidth = no_specified_width,
|
|
Errors = [error_nonint_star_width(SpecNum,
|
|
poly_type_to_kind(PolyType))]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
MaybeWidth = no_specified_width,
|
|
Errors = [error_missing_star_width(SpecNum)]
|
|
)
|
|
else if get_nonzero_number_prefix(!Chars, Width) then
|
|
MaybeWidth = specified_width(Width),
|
|
Errors = []
|
|
else
|
|
MaybeWidth = no_specified_width,
|
|
Errors = []
|
|
).
|
|
|
|
% Do we have a precision? If yes, get it.
|
|
%
|
|
:- pred get_optional_prec(list(char)::in, list(char)::out,
|
|
list(string.poly_type)::in, list(string.poly_type)::out, int::in,
|
|
string_format_maybe_prec::out, list(string_format_error)::out) is det.
|
|
|
|
get_optional_prec(!Chars, !PolyTypes, SpecNum, MaybePrec, Errors) :-
|
|
( if !.Chars = ['.' | !:Chars] then
|
|
( if !.Chars = ['*' | !:Chars] then
|
|
(
|
|
!.PolyTypes = [PolyType | !:PolyTypes],
|
|
( if PolyType = i(PolyPrec) then
|
|
MaybePrec = specified_prec(PolyPrec),
|
|
Errors = []
|
|
else
|
|
MaybePrec = no_specified_prec,
|
|
Errors = [error_nonint_star_prec(SpecNum,
|
|
poly_type_to_kind(PolyType))]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
MaybePrec = no_specified_prec,
|
|
Errors = [error_missing_star_prec(SpecNum)]
|
|
)
|
|
else
|
|
% This treats an empty string as an EXPLICIT zero.
|
|
get_number_prefix(!Chars, Prec),
|
|
MaybePrec = specified_prec(Prec),
|
|
Errors = []
|
|
)
|
|
else
|
|
MaybePrec = no_specified_prec,
|
|
Errors = []
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% get_first_spec(!Chars, !PolyTypes, Flags, MaybeWidth, MaybePrec,
|
|
% SpecNum, Spec, Errors):
|
|
%
|
|
% Try to read one conversion specifier, whose percent sign, flags,
|
|
% width and precision have already been read, from !Chars.
|
|
%
|
|
% If successful, consume the corresponding poly_type from !PolyTypes,
|
|
% we return the specifier as Spec and return an empty error list.
|
|
%
|
|
% If there is a problem, we return a garbage Spec and a nonempty
|
|
% errors list. We also consume the poly_type that corresponds
|
|
% (or at least, looks like it corresponds) to the specifier,
|
|
% if there is one.
|
|
%
|
|
:- pred get_first_spec(list(char)::in, list(char)::out,
|
|
list(string.poly_type)::in, list(string.poly_type)::out,
|
|
string_format_flags::in, string_format_maybe_width::in,
|
|
string_format_maybe_prec::in, int::in,
|
|
string_format_spec::out, list(string_format_error)::out) is det.
|
|
|
|
get_first_spec(!Chars, !PolyTypes, _Flags, _MaybeWidth, _MaybePrec, SpecNum,
|
|
Spec, Errors) :-
|
|
!.Chars = [],
|
|
Spec = spec_constant_string(""),
|
|
Errors = [error_no_specifier(SpecNum, list.length(!.PolyTypes))].
|
|
get_first_spec(!Chars, !PolyTypes, !.Flags, MaybeWidth, MaybePrec, SpecNum,
|
|
Spec, Errors) :-
|
|
!.Chars = [SpecChar | !:Chars],
|
|
( if
|
|
require_switch_arms_det [SpecChar]
|
|
(
|
|
SpecChar = '%',
|
|
SpecPrime = spec_constant_string("%"),
|
|
ErrorsPrime = []
|
|
;
|
|
( SpecChar = 'd'
|
|
; SpecChar = 'i'
|
|
),
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = i(Int) then
|
|
% Base is always decimal
|
|
SpecPrime = spec_signed_int(!.Flags,
|
|
MaybeWidth, MaybePrec, Int),
|
|
ErrorsPrime = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
(
|
|
SpecChar = 'o',
|
|
Base = base_octal
|
|
;
|
|
SpecChar = 'u',
|
|
Base = base_decimal
|
|
;
|
|
SpecChar = 'x',
|
|
Base = base_hex_lc
|
|
;
|
|
SpecChar = 'X',
|
|
Base = base_hex_uc
|
|
;
|
|
SpecChar = 'p',
|
|
Base = base_hex_p,
|
|
% XXX This should not be necessary.
|
|
!Flags ^ flag_hash := flag_hash_set
|
|
),
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = i(Int) then
|
|
SpecPrime = spec_unsigned_int(!.Flags,
|
|
MaybeWidth, MaybePrec, Base, Int),
|
|
ErrorsPrime = []
|
|
else if SpecPolyType = u(UInt) then
|
|
SpecPrime = spec_uint(!.Flags,
|
|
MaybeWidth, MaybePrec, Base, UInt),
|
|
ErrorsPrime = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
(
|
|
SpecChar = 'e',
|
|
FloatKind = kind_e_scientific_lc
|
|
;
|
|
SpecChar = 'E',
|
|
FloatKind = kind_e_scientific_uc
|
|
;
|
|
SpecChar = 'f',
|
|
FloatKind = kind_f_plain_lc
|
|
;
|
|
SpecChar = 'F',
|
|
FloatKind = kind_f_plain_uc
|
|
;
|
|
SpecChar = 'g',
|
|
FloatKind = kind_g_flexible_lc
|
|
;
|
|
SpecChar = 'G',
|
|
FloatKind = kind_g_flexible_uc
|
|
),
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = f(Float) then
|
|
SpecPrime = spec_float(!.Flags,
|
|
MaybeWidth, MaybePrec, FloatKind, Float),
|
|
ErrorsPrime = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
SpecChar = 'c',
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = c(Char) then
|
|
% XXX Should we generate an error if MaybePrec = yes(...)?
|
|
SpecPrime = spec_char(!.Flags, MaybeWidth, Char),
|
|
ErrorsPrime = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
SpecChar = 's',
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = s(Str) then
|
|
SpecPrime = spec_string(!.Flags,
|
|
MaybeWidth, MaybePrec, Str),
|
|
ErrorsPrime = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
SpecPrime = spec_constant_string(""),
|
|
ErrorsPrime = [Error]
|
|
)
|
|
)
|
|
then
|
|
Spec = SpecPrime,
|
|
Errors = ErrorsPrime
|
|
else
|
|
Error = error_unknown_specifier(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
).
|
|
|
|
:- func poly_type_to_kind(poly_type) = poly_kind.
|
|
|
|
poly_type_to_kind(c(_)) = poly_kind_char.
|
|
poly_type_to_kind(s(_)) = poly_kind_str.
|
|
poly_type_to_kind(i(_)) = poly_kind_int.
|
|
poly_type_to_kind(u(_)) = poly_kind_uint.
|
|
poly_type_to_kind(f(_)) = poly_kind_float.
|
|
|
|
%---------------------------------------------------------------------------%
|