mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 17:33:38 +00:00
481 lines
17 KiB
Mathematica
481 lines
17 KiB
Mathematica
%---------------------------------------------------------------------------%
|
|
% vim: ts=4 sw=4 et ft=mercury
|
|
%---------------------------------------------------------------------------%
|
|
% Copyright (C) 2014-2015, 2018-2020, 2025 The Mercury team.
|
|
% This file is distributed under the terms specified in COPYING.LIB.
|
|
%---------------------------------------------------------------------------%
|
|
%
|
|
% File: string.parse_runtime.m.
|
|
% Stability: high.
|
|
%
|
|
% 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 list.
|
|
:- import_module string.parse_util.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- 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,
|
|
sized_int
|
|
)
|
|
; spec_unsigned_int(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_int_base,
|
|
sized_int
|
|
)
|
|
; spec_uint(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_int_base,
|
|
sized_uint
|
|
)
|
|
; spec_float(
|
|
string_format_flags,
|
|
string_format_maybe_width,
|
|
string_format_maybe_prec,
|
|
string_format_float_kind,
|
|
float
|
|
).
|
|
|
|
:- type sized_int
|
|
---> sized_int(int)
|
|
; sized_int8(int8)
|
|
; sized_int16(int16)
|
|
; sized_int32(int32)
|
|
; sized_int64(int64).
|
|
|
|
:- type sized_uint
|
|
---> sized_uint(uint)
|
|
; sized_uint8(uint8)
|
|
; sized_uint16(uint16)
|
|
; sized_uint32(uint32)
|
|
; sized_uint64(uint64).
|
|
|
|
% 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(poly_type)::in, int::in,
|
|
list(string_format_spec)::out, list(string_format_error)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module int.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
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(poly_type)::in, list(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(poly_type)::in, list(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(poly_type)::in, list(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(poly_type)::in, list(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, Flags0, MaybeWidth, MaybePrec, SpecNum,
|
|
Spec, Errors) :-
|
|
!.Chars = [SpecChar | !:Chars],
|
|
( if
|
|
parse_spec(SpecChar, !PolyTypes, Flags0, MaybeWidth, MaybePrec,
|
|
SpecNum, SpecPrime, ErrorsPrime)
|
|
then
|
|
Spec = SpecPrime,
|
|
Errors = ErrorsPrime
|
|
else
|
|
Error = error_unknown_specifier(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
).
|
|
|
|
:- pred parse_spec(char::in, list(poly_type)::in, list(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 semidet.
|
|
|
|
parse_spec(SpecChar, !PolyTypes, !.Flags, MaybeWidth, MaybePrec,
|
|
SpecNum, Spec, Errors) :-
|
|
require_switch_arms_det [SpecChar]
|
|
(
|
|
SpecChar = '%',
|
|
Spec = spec_constant_string("%"),
|
|
Errors = []
|
|
;
|
|
( SpecChar = 'd'
|
|
; SpecChar = 'i'
|
|
),
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
(
|
|
( SpecPolyType = i(Int), SizedInt = sized_int(Int)
|
|
; SpecPolyType = i8(Int8), SizedInt = sized_int8(Int8)
|
|
; SpecPolyType = i16(Int16), SizedInt = sized_int16(Int16)
|
|
; SpecPolyType = i32(Int32), SizedInt = sized_int32(Int32)
|
|
; SpecPolyType = i64(Int64), SizedInt = sized_int64(Int64)
|
|
),
|
|
% Base is always decimal.
|
|
Spec = spec_signed_int(!.Flags, MaybeWidth, MaybePrec,
|
|
SizedInt),
|
|
Errors = []
|
|
;
|
|
( SpecPolyType = f(_)
|
|
; SpecPolyType = u(_)
|
|
; SpecPolyType = u8(_)
|
|
; SpecPolyType = u16(_)
|
|
; SpecPolyType = u32(_)
|
|
; SpecPolyType = u64(_)
|
|
; SpecPolyType = s(_)
|
|
; SpecPolyType = c(_)
|
|
),
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [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],
|
|
(
|
|
( SpecPolyType = i(Int), SizedInt = sized_int(Int)
|
|
; SpecPolyType = i8(Int8), SizedInt = sized_int8(Int8)
|
|
; SpecPolyType = i16(Int16), SizedInt = sized_int16(Int16)
|
|
; SpecPolyType = i32(Int32), SizedInt = sized_int32(Int32)
|
|
; SpecPolyType = i64(Int64), SizedInt = sized_int64(Int64)
|
|
),
|
|
Spec = spec_unsigned_int(!.Flags, MaybeWidth, MaybePrec, Base,
|
|
SizedInt),
|
|
Errors = []
|
|
;
|
|
( SpecPolyType = u(UInt), SizedUInt = sized_uint(UInt)
|
|
; SpecPolyType = u8(UInt8), SizedUInt = sized_uint8(UInt8)
|
|
; SpecPolyType = u16(UInt16), SizedUInt = sized_uint16(UInt16)
|
|
; SpecPolyType = u32(UInt32), SizedUInt = sized_uint32(UInt32)
|
|
; SpecPolyType = u64(UInt64), SizedUInt = sized_uint64(UInt64)
|
|
),
|
|
Spec = spec_uint(!.Flags, MaybeWidth, MaybePrec, Base,
|
|
SizedUInt),
|
|
Errors = []
|
|
;
|
|
( SpecPolyType = f(_)
|
|
; SpecPolyType = s(_)
|
|
; SpecPolyType = c(_)
|
|
),
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [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
|
|
Spec = spec_float(!.Flags, MaybeWidth, MaybePrec,
|
|
FloatKind, Float),
|
|
Errors = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
SpecChar = 'c',
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = c(Char) then
|
|
% XXX Should we generate an error if MaybePrec = yes(...)?
|
|
Spec = spec_char(!.Flags, MaybeWidth, Char),
|
|
Errors = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(SpecNum, SpecChar),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
SpecChar = 's',
|
|
(
|
|
!.PolyTypes = [SpecPolyType | !:PolyTypes],
|
|
( if SpecPolyType = s(Str) then
|
|
Spec = spec_string(!.Flags, MaybeWidth, MaybePrec, Str),
|
|
Errors = []
|
|
else
|
|
Error = error_wrong_polytype(SpecNum, SpecChar,
|
|
poly_type_to_kind(SpecPolyType)),
|
|
Spec = spec_constant_string(""),
|
|
Errors = [Error]
|
|
)
|
|
;
|
|
!.PolyTypes = [],
|
|
Error = error_no_polytype(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(i8(_)) = poly_kind_int8.
|
|
poly_type_to_kind(i16(_)) = poly_kind_int16.
|
|
poly_type_to_kind(i32(_)) = poly_kind_int32.
|
|
poly_type_to_kind(i64(_)) = poly_kind_int64.
|
|
poly_type_to_kind(u(_)) = poly_kind_uint.
|
|
poly_type_to_kind(u8(_)) = poly_kind_uint8.
|
|
poly_type_to_kind(u16(_)) = poly_kind_uint16.
|
|
poly_type_to_kind(u32(_)) = poly_kind_uint32.
|
|
poly_type_to_kind(u64(_)) = poly_kind_uint64.
|
|
poly_type_to_kind(f(_)) = poly_kind_float.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module string.parse_runtime.
|
|
%---------------------------------------------------------------------------%
|