[m-rev.] for review: new implementation of string__format in mercury.

p.incani paulai at ugrad.unimelb.edu.au
Thu Jan 31 19:32:02 AEDT 2002


Estimated hours taken: 200

Removed the old implementation of string__format because it relies on C's
printf function - this causes problems when Mercury is compiled to Jave or
.NET. Replaced it with a new version of string__format that works
independently of C's printf, and which is written solely in Mercury.

library/string.m:
	Removed old implementation of string__format.
	Slightly modified the format string parser.
	Replaced old string__format with a new Mercury implementation.
	Modified type 'specifier'.
	Added quite a few maths utilities that are needed to format
	floats.

Index: string.m
===================================================================
RCS file: /home/mercury1/repository/mercury/library/string.m,v
retrieving revision 1.165
diff -u -r1.165 string.m
--- string.m	20 Jan 2002 07:32:26 -0000	1.165
+++ string.m	30 Jan 2002 07:47:53 -0000
@@ -458,7 +458,7 @@
 %-----------------------------------------------------------------------------%

 :- implementation.
-:- import_module bool, std_util, int, float, require.
+:- import_module bool, std_util, int, float, require, integer.

 :- pred string__to_int_list(string, list(int)).
 :- mode string__to_int_list(out, in) is det.
@@ -1135,8 +1135,8 @@
 :- type specifier
 	--->	conv(
 			flags 		:: list(char),
-			width		:: maybe(list(char)),
-			precision	:: maybe(list(char)),
+			width		:: maybe(int),
+			precision	:: maybe(int),
 			spec		:: spec
 		)
 	;	string(list(char)).
@@ -1231,10 +1231,11 @@
 flag(' ').
 flag('+').

+
 	%
 	% Do we have a minimum field width?
 	%
-:- pred width(list(char)::out,
+:- pred width(int::out,
 		list(string__poly_type)::in, list(string__poly_type)::out,
 		list(char)::in, list(char)::out) is semidet.

@@ -1242,7 +1243,7 @@
 	( ['*'] ->
 		{ PolyTypes0 = [i(Width0) | PolyTypes1] ->
 				% XXX maybe better done in C.
-			Width = to_char_list(int_to_string(Width0)),
+			Width = Width0,
 			PolyTypes = PolyTypes1
 		;
 			error("string__format: `*' width modifer not associated with an integer.")
@@ -1253,14 +1254,17 @@
 		zero_or_more_occurences(digit),
 		=(Final),

-		{ list__remove_suffix(Init, Final, Width) },
+		{ list__remove_suffix(Init, Final, WidthCharList) },
+		{ string__from_char_list(WidthCharList, WidthString)},
+		{ string__to_int(WidthString, Width)},
 		{ PolyTypes = PolyTypes0 }
 	).

+
 	%
 	% Do we have a precision?
 	%
-:- pred prec(list(char)::out,
+:- pred prec(int::out,
 		list(string__poly_type)::in, list(string__poly_type)::out,
 		list(char)::in, list(char)::out) is semidet.

@@ -1269,7 +1273,7 @@
 	( ['*'] ->
 		{ PolyTypes0 = [i(Prec0) | PolyTypes1] ->
 				% XXX Best done in C
-			Prec = to_char_list(int_to_string(Prec0)),
+			Prec = Prec0,
 			PolyTypes = PolyTypes1
 		;
 			error("string__format: `*' precision modifer not associated with an integer.")
@@ -1280,15 +1284,18 @@
 		zero_or_more_occurences(digit),
 		=(Final)
 	->
-		{ list__remove_suffix(Init, Final, Prec) },
+		{ list__remove_suffix(Init, Final, PrecCharList) },
+		{ string__from_char_list(PrecCharList, PrecString)},
+		{ string__to_int(PrecString, Prec)},
 		{ PolyTypes = PolyTypes0 }
 	;
 			% When no number follows the '.' the precision
 			% defaults to 0.
-		{ Prec = ['0'] },
+		{ Prec = 0 },
 		{ PolyTypes = PolyTypes0 }
 	).

+
 % NB the capital letter specifiers are proceeded with a 'c'.
 :- type spec
 		% valid integer specifiers
@@ -1386,247 +1393,924 @@
 	(
 			% valid int conversion specifiers
 		Spec = d(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "d"), Int)
+		String = format_int(Flags, Width, Prec, Int)
 	;
 		Spec = i(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "i"), Int)
+		String = format_int(Flags, Width, Prec, Int)
 	;
 		Spec = o(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "o"), Int)
+		String = format_unsigned_int(Flags, Width, Prec, 8, Int, "")
 	;
 		Spec = u(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "u"), Int)
+		String = format_unsigned_int(Flags, Width, Prec, 10, Int, "")
 	;
 		Spec = x(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "x"), Int)
+		String = format_unsigned_int(Flags, Width, Prec, 16, Int, "0x")
 	;
 		Spec = cX(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "X"), Int)
+		String = format_unsigned_int(Flags, Width, Prec, 16, Int, "0X")
 	;
 		Spec = p(Int),
-		String = format_int(
-				make_format(Flags, Width,
-					Prec, int_length_modifer, "p"), Int)
+		String = format_unsigned_int(['#'|Flags], Width, Prec, 16, Int, "0x")
 	;
-			% valid float conversion specifiers
 		Spec = e(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "e"), Float)
+		String = format_scientific_number(Flags, Width, Prec, Float, "e")
 	;
 		Spec = cE(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "E"), Float)
+		String = format_scientific_number(Flags, Width, Prec, Float, "E")
 	;
 		Spec = f(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "f"), Float)
+		String = format_float(Flags, Width, Prec, Float)
 	;
 		Spec = cF(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "F"), Float)
+		String = format_float(Flags, Width, Prec, Float)
 	;
 		Spec = g(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "g"), Float)
+		String = format_scientific_number_g(Flags, Width, Prec, Float, "e")
 	;
 		Spec = cG(Float),
-		String = format_float(
-			make_format(Flags, Width, Prec, "", "G"), Float)
+		String = format_scientific_number_g(Flags, Width, Prec, Float, "E")
 	;
-			% valid char conversion Specifiers
 		Spec = c(Char),
-		String = format_char(
-				make_format(Flags, Width, Prec, "", "c"), Char)
+		String = format_char(Flags, Width, Char)
 	;
-			% valid string conversion Spec = ifiers
 		Spec = s(Str),
-		String = format_string(
-				make_format(Flags, Width, Prec, "", "s"), Str)
+		String = format_string(Flags, Width, Prec, Str)
 	;
-			% conversion specifier representing the "%" sign
 		Spec = percent,
 		String = "%"
 	).
 specifier_to_string(string(Chars)) = from_char_list(Chars).

+	%
+	% Format a signed int (d,i).
+	%
+:- func format_int(list(char), maybe(int), maybe(int), int) = string.
+format_int(Flags, Width, Prec, Int) = String :-
+		%
+		% Find integers absolute value, and take care of special
+		% case of precision zero with an integer of 0.
+		%
+	( Int = 0, Prec = yes(0)
+	->
+		AbsIntStr = ""
+	;
+		Integer = integer(Int),
+		AbsInteger = integer__abs(Integer),
+		AbsIntStr = integer__to_string(AbsInteger)
+	),
+	AbsIntStrLength = string__length(AbsIntStr),
+		%
+		% Do we need to increase precision?
+		%
+	(Prec = yes(Precision),
+	 Precision > AbsIntStrLength
+	->
+		PrecStr = string__pad_left(AbsIntStr, '0', Precision)
+	;
+		PrecStr = AbsIntStr
+	),
+		%
+		% Do we need to pad to the field width.
+		%
+	( Width = yes(FieldWidth),
+	  FieldWidth > string__length(PrecStr),
+	  member('0', Flags),
+	  \+member('-', Flags),
+	  Prec = no
+	->
+		FieldStr = string__pad_left(PrecStr, '0', FieldWidth - 1),
+		ZeroPadded = yes
+	;
+		FieldStr = PrecStr,
+		ZeroPadded = no
+	),
+		%
+		% Prefix with appropriate sign or zero padding.
+		% The previous step has deliberately left room for this.
+		%
+	( Int < 0 ->
+		SignedStr = string__append("-", FieldStr)
+	;
+	member('+', Flags) ->
+		SignedStr = string__append("+", FieldStr)
+	;
+	member(' ', Flags) ->
+		SignedStr = string__append(" ", FieldStr)
+	;
+	ZeroPadded = yes  ->
+		SignedStr = string__append("0", FieldStr)
+	;
+		SignedStr = FieldStr
+	),
+		%
+		% Do we need to justify?
+		%
+	String = justify_string(Flags, Width, SignedStr).
+
+
+	%
+	% Format an unsigned int, unsigned octal, unsigned hexadecimal (u,o,x,X).
+	%
+:- func format_unsigned_int(list(char), maybe(int), maybe(int), int, int, string) = string.
+format_unsigned_int(Flags, Width, Prec, Base, Int, Prefix) = String :-
+		%
+		% Find integers absolute value, and take care of special
+		% case of precision zero with an integer of 0.
+		%
+	( Int = 0, Prec = yes(0)
+	->
+		AbsIntStr = ""
+	;
+		integer__pow(integer(2), integer(int__bits_per_int), Div),
+		UnsignedInteger = integer(Int) mod Div,
+		( Base = 10
+		  ->
+			AbsIntStr0 = integer__to_string(UnsignedInteger)
+		; Base = 8
+		  ->
+			AbsIntStr0 = to_octal(UnsignedInteger)
+		; Prefix = "0x"
+	 	  ->
+			AbsIntStr0 = to_hex(UnsignedInteger)
+		;
+			AbsIntStr0 = to_capital_hex(UnsignedInteger)
+		),
+			%
+			% Just in case Int = 0 (base converters return "").
+			%
+		( AbsIntStr0 = ""
+		->
+			AbsIntStr = "0"
+		;
+			AbsIntStr = AbsIntStr0
+		)
+	),
+	AbsIntStrLength = string__length(AbsIntStr),
+		%
+		% Do we need to increase precision?
+		%
+	(Prec = yes(Precision),
+	 Precision > AbsIntStrLength
+	->
+		PrecStr = string__pad_left(AbsIntStr, '0', Precision)
+	;
+		PrecStr = AbsIntStr
+	),
+		%
+		% Do we need to increase the precision of an octal?
+		%
+	( Base = 8,
+	  member('#', Flags),
+	\+string__prefix(PrecStr, "0")
+	->
+		PrecModStr = append("0", PrecStr)
+	;
+		PrecModStr = PrecStr
+	),
+		%
+		% Do we need to pad to the field width.
+		%
+	( Width = yes(FieldWidth),
+	  FieldWidth > string__length(PrecModStr),
+	  member('0', Flags),
+	  \+member('-', Flags),
+	  Prec = no
+	->
+			%
+			% Do we need to make room for "0x" or "0X" ?
+			%
+		(Base = 16, member('#', Flags)
+		->
+			FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 2)
+		;
+			FieldStr = string__pad_left(PrecModStr, '0', FieldWidth)
+		)
+	;
+		FieldStr = PrecModStr
+	),
+		%
+		% Do we have to prefix "0x" or "0X"?
+		%
+	( Base = 16,
+	  member('#', Flags),
+	  FieldStr \= "0",
+	  FieldStr \= ""
+	->
+	 	FieldModStr = string__append(Prefix, FieldStr)
+	;
+		FieldModStr = FieldStr
+	),
+		%
+		% Do we need to justify?
+		%
+	String = justify_string(Flags, Width, FieldModStr).
+

-	% Construct a format string.
-:- func make_format(list(char), maybe(list(char)),
-		maybe(list(char)), string, string) = string.
+	%
+	% Format a character (c).
+	%
+:- func format_char(list(char), maybe(int), char) = string.
+format_char(Flags, Width, Char) = String :-
+	CharStr = string__char_to_string(Char),
+	String = justify_string(Flags, Width, CharStr).

-make_format(Flags, MaybeWidth, MaybePrec, LengthMod, Spec) =
-	( using_sprintf ->
-		make_format_sprintf(Flags, MaybeWidth, MaybePrec, LengthMod,
-			Spec)
+
+	%
+	% Format a string (s).
+	%
+:- func format_string(list(char), maybe(int), maybe(int), string) = string.
+format_string(Flags, Width, Prec, OldStr) = NewStr :-
+	( Prec = yes(NumChars)
+	->
+		PrecStr = string__substring(OldStr, 0, NumChars)
 	;
-		make_format_dotnet(Flags, MaybeWidth, MaybePrec, LengthMod,
-			Spec)
+		PrecStr = OldStr
+	),
+	NewStr = justify_string(Flags, Width, PrecStr).
+
+
+	%
+	% Format a float (f)
+	%
+:- func format_float(list(char), maybe(int), maybe(int), float) = string.
+format_float(Flags, Width, Prec, Float) = NewFloat :-
+		%
+		% Determine absolute value of string.
+		%
+	Abs = abs(Float),
+		%
+		% Change precision (default is 6)
+		%
+	AbsStr = convert_float_to_string(Abs),
+	( Prec = yes(Precision)
+	->
+		PrecStr = change_precision(Precision, AbsStr)
+	;
+		PrecStr = change_precision(6, AbsStr)
+	),
+		%
+		% Do we need to remove the decimal point?
+		%
+	( \+member('#', Flags),
+	  Prec = yes(0)
+	->
+		PrecStrLen = string__length(PrecStr),
+		PrecModStr = string__substring(PrecStr, 0, PrecStrLen - 1)
+	;
+		PrecModStr = PrecStr
+	),
+		%
+		% Do we need to change field width?
+		%
+	( Width = yes(FieldWidth),
+	  FieldWidth > string__length(PrecModStr),
+	  member('0', Flags),
+	  \+member('-', Flags)
+	->
+		FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 1),
+		ZeroPadded = yes
+	;
+		FieldStr = PrecModStr,
+		ZeroPadded = no
+	),
+		%
+		% Finishing up ..
+		%
+	( Float < 0.0 ->
+		SignedStr = string__append("-", FieldStr)
+	;
+	member('+', Flags) ->
+		SignedStr = string__append("+", FieldStr)
+	;
+	member(' ', Flags) ->
+		SignedStr = string__append(" ", FieldStr)
+	;
+	ZeroPadded = yes ->
+		SignedStr = string__append("0", FieldStr)
+	;
+		SignedStr = FieldStr
+	),
+		%
+		% Do we need to justify?
+		%
+	NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+	%
+	% Format a scientific number (e,E)
+	%
+:- func format_scientific_number(list(char), maybe(int), maybe(int), float, string) = string.
+format_scientific_number(Flags, Width, Prec, Float, E) = NewFloat :-
+		%
+		% Determine absolute value of string.
+		%
+	Abs = abs(Float),
+		%
+		% Change precision (default is 6)
+		%
+	AbsStr = convert_float_to_string(Abs),
+	( Prec = yes(Precision)
+	->
+		PrecStr = change_to_e_notation(AbsStr, Precision, E)
+	;
+		PrecStr = change_to_e_notation(AbsStr, 6, E)
+	),
+		%
+		% Do we need to remove the decimal point?
+		%
+	( \+member('#', Flags),
+	  Prec = yes(0)
+	->
+		BaseAndExponent = string__words(is_decimal_point, PrecStr),
+		list__index0_det(BaseAndExponent, 0, BaseStr),
+		list__index0_det(BaseAndExponent, 1, ExponentStr),
+		PrecModStr = string__append(BaseStr, ExponentStr)
+	;
+		PrecModStr = PrecStr
+	),
+		%
+		% Do we need to change field width?
+		%
+	( Width = yes(FieldWidth),
+	  FieldWidth > string__length(PrecModStr),
+	  member('0', Flags),
+	  \+member('-', Flags)
+	->
+		FieldStr = string__pad_left(PrecModStr, '0', FieldWidth - 1),
+		ZeroPadded = yes
+	;
+		FieldStr = PrecModStr,
+		ZeroPadded = no
+	),
+		%
+		% Finishing up ..
+		%
+	( Float < 0.0 ->
+		SignedStr = string__append("-", FieldStr)
+	;
+	member('+', Flags) ->
+		SignedStr = string__append("+", FieldStr)
+	;
+	member(' ', Flags) ->
+		SignedStr = string__append(" ", FieldStr)
+	;
+	ZeroPadded = yes ->
+		SignedStr = string__append("0", FieldStr)
+	;
+		SignedStr = FieldStr
+	),
+		%
+		% Do we need to justify?
+		%
+	NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+	%
+	% Format a scientific number to a specified number of significant figures (g,G)
+	%
+:- func format_scientific_number_g(list(char), maybe(int), maybe(int), float, string) = string.
+format_scientific_number_g(Flags, Width, Prec, Float, E) = NewFloat :-
+		%
+		% Determine absolute value of string.
+		%
+	Abs = abs(Float),
+		%
+		% Change precision (default is 6)
+		%
+	AbsStr = convert_float_to_string(Abs),
+	( Prec = yes(Precision)
+	->
+		(Precision = 0
+		->
+			PrecStr = change_to_g_notation(AbsStr, 1, E, Flags)
+		;
+			PrecStr = change_to_g_notation(AbsStr, Precision, E, Flags)
+		)
+	;
+		PrecStr = change_to_g_notation(AbsStr, 6, E, Flags)
+	),
+		%
+		% Do we need to change field width?
+		%
+	( Width = yes(FieldWidth),
+	  FieldWidth > string__length(PrecStr),
+	  member('0', Flags),
+	  \+member('-', Flags)
+	->
+		FieldStr = string__pad_left(PrecStr, '0', FieldWidth - 1),
+		ZeroPadded = yes
+	;
+		FieldStr = PrecStr,
+		ZeroPadded = no
+	),
+		%
+		% Finishing up ..
+		%
+	( Float < 0.0 ->
+		SignedStr = string__append("-", FieldStr)
+	;
+	member('+', Flags) ->
+		SignedStr = string__append("+", FieldStr)
+	;
+	member(' ', Flags) ->
+		SignedStr = string__append(" ", FieldStr)
+	;
+	ZeroPadded = yes ->
+		SignedStr = string__append("0", FieldStr)
+	;
+		SignedStr = FieldStr
+	),
+		%
+		% Do we need to justify?
+		%
+	NewFloat = justify_string(Flags, Width, SignedStr).
+
+
+% ------- UTILITY FUNCTIONS.
+
+
+	%
+	% Given a floating point number, this function calculates the size of the exponent
+	% needed to represent the float in scientific notation.
+	%
+:- func size_of_required_exponent(string, int) = int.
+size_of_required_exponent(Float, Prec) = Exponent :-
+	UnsafeExponent = decimal_pos(Float),
+	UnsafeBase = calculate_base_unsafe(Float, Prec),
+		%
+		% Is mantissa one digit long?
+		%
+	MantAndFrac = string__words(is_decimal_point, UnsafeBase),
+	list__index0_det(MantAndFrac, 0, MantissaStr),
+	( string__length(MantissaStr) > 1
+	->
+		% we will need need to move decimal pt one place to the left: therefore,
+		% increment exponent.
+		Exponent = UnsafeExponent + 1
+	;
+		Exponent = UnsafeExponent
 	).

+	%
+	% Given a string representing a floating point number, function returns a string with
+	% all trailing zeros removed.
+	%
+:- func remove_trailing_zeros(string) = string.
+remove_trailing_zeros(Float) = TrimmedFloat :-
+	FloatCharList = string__to_char_list(Float),
+	FloatCharListRev = list__reverse(FloatCharList),
+	TrimmedFloatRevCharList = remove_zeros(FloatCharListRev),
+	TrimmedFloatCharList = list__reverse(TrimmedFloatRevCharList),
+	TrimmedFloat = string__from_char_list(TrimmedFloatCharList).

-:- pred using_sprintf is semidet.
+	%
+	% Given a char list, this function removes all leading zeros, including decimal point,
+	% if need be.
+	%
+:- func remove_zeros(list(char)) = list(char).
+remove_zeros(CharNum) = TrimmedNum :-
+	( CharNum = ['0'|Rest]
+	->
+		TrimmedNum = remove_zeros(Rest)
+	;
+ 	 CharNum = ['.'|Rest]
+	->
+		TrimmedNum = Rest
+	;
+		TrimmedNum = CharNum
+	).

-:- pragma foreign_proc("C", using_sprintf,
-	[will_not_call_mercury, promise_pure, thread_safe], "
-	SUCCESS_INDICATOR = TRUE;
-").
-:- pragma foreign_proc("MC++", using_sprintf,
-	[will_not_call_mercury, promise_pure, thread_safe], "
-	SUCCESS_INDICATOR = FALSE;
-").
-

-	% Construct a format string suitable to passing to sprintf.
-:- func make_format_sprintf(list(char), maybe(list(char)),
-		maybe(list(char)), string, string) = string.
+	%
+	% Converts a floating point number to a specified number of standard figures.
+	% The style used depends on the value converted; style e (or E) is used only if the
+	% exponent resulting from such a conversion is less than -4 or greater than or equal
+	% to the precision. Trailing zeros are removed from the fractional portion of the
+	% result unless the # flag is specified: a decimal-point character appears only if it
+	% is followed by a digit.
+	%
+:- func change_to_g_notation(string, int, string, list(char)) = string.
+change_to_g_notation(Float, Prec, E, Flags) = FormattedFloat :-
+	Exponent = size_of_required_exponent(Float, Prec),
+	( Exponent >= -4, Exponent < Prec
+	->
+			% Float will be represented normally.
+			% -----------------------------------
+			% Need to calculate precision to pass to the change_precision
+			% function, because the current precision represents significant
+			% figures, not decimal places.
+			%
+			% now change float's precision.
+			%
+		( Exponent =< 0
+		->
+				%
+				% deal with floats such as 0.00000000xyz
+				%
+			DecimalPos = decimal_pos(Float),
+			FormattedFloat0 = change_precision(abs(DecimalPos)-1+Prec, Float)
+		;
+				%
+				% deal with floats such as ddddddd.mmmmmmmm
+				%
+			ScientificFloat = change_to_e_notation(Float, Prec - 1, "e"),
+			BaseAndExponent = string__words(is_exponent, ScientificFloat),
+			list__index0_det(BaseAndExponent, 0, BaseStr),
+			list__index0_det(BaseAndExponent, 1, ExponentStr),
+			Exp = string__det_to_int(ExponentStr),
+			MantAndFrac = string__words(is_decimal_point, BaseStr),
+			list__index0_det(MantAndFrac, 0, MantissaStr),
+			list__index0_det(MantAndFrac, 1, FractionStr),
+			RestMantissaStr = string__substring(FractionStr, 0, Exp),
+			NewFraction = string__substring(FractionStr, Exp, Prec - Exp -1),
+			FormattedFloat0 = MantissaStr ++ RestMantissaStr ++ "." ++ NewFraction
+		),
+			%
+			% Do we remove trailing zeros?
+			%
+		( member('#', Flags)
+		->
+			FormattedFloat = FormattedFloat0
+		;
+			FormattedFloat = remove_trailing_zeros(FormattedFloat0)
+		)
+	;
+			% Float will be represented in scientific notation.
+			% -------------------------------------------------
+			%
+		UncheckedFloat = change_to_e_notation(Float, Prec - 1, E),
+			%
+			% Do we need to remove trailing zeros?
+			%
+		( member('#', Flags)
+		->
+			FormattedFloat = UncheckedFloat
+		;
+			BaseAndExponent = string__words(is_exponent, UncheckedFloat),
+			list__index0_det(BaseAndExponent, 0, BaseStr),
+			list__index0_det(BaseAndExponent, 1, ExponentStr),
+			NewBaseStr = remove_trailing_zeros(BaseStr),
+			FormattedFloat = NewBaseStr ++ E ++ ExponentStr
+		)
+	).

-make_format_sprintf(Flags, MaybeWidth, MaybePrec, LengthMod, Spec) = String :-
-	(
-		MaybeWidth = yes(Width)
+
+
+	%
+	% convert floating point notation to scientific notation.
+	%
+:- func change_to_e_notation(string, int, string) = string.
+change_to_e_notation(Float, Prec, E) = ScientificFloat :-
+	UnsafeExponent = decimal_pos(Float),
+	UnsafeBase = calculate_base_unsafe(Float, Prec),
+		%
+		% Is mantissa one digit long?
+		%
+	MantAndFrac = string__words(is_decimal_point, UnsafeBase),
+	list__index0_det(MantAndFrac, 0, MantissaStr),
+	( string__length(MantissaStr) > 1
+	->
+		% need to append 0, to fix the problem of having no numbers after the
+		% decimal point.
+		SafeBase = calculate_base_unsafe(string__append(UnsafeBase, "0"), Prec),
+		SafeExponent = UnsafeExponent + 1
 	;
-		MaybeWidth = no,
-		Width = []
+		SafeBase = UnsafeBase,
+		SafeExponent = UnsafeExponent
 	),
-	(
-		MaybePrec = yes(Prec0),
-		Prec = ['.' | Prec0]
+		%
+		% Creating exponent.
+		%
+	( SafeExponent >= 0
+	->
+		(SafeExponent < 10
+		->
+			ExponentStr = string__append_list([E, "+0", string__int_to_string(SafeExponent)])
+		;
+			ExponentStr = string__append_list([E, "+", string__int_to_string(SafeExponent)])
+		)
 	;
-		MaybePrec = no,
-		Prec = []
+		(SafeExponent > -10
+		->
+			ExponentStr = string__append_list([E, "-0", string__int_to_string(int__abs(SafeExponent))])
+		;
+			ExponentStr = string__append(E, string__int_to_string(SafeExponent))
+		)
 	),
-	String = string__append_list(["%", from_char_list(Flags),
-				from_char_list(Width),
-				from_char_list(Prec), LengthMod, Spec]).
+	ScientificFloat = string__append(SafeBase, ExponentStr).


-	% Construct a format string suitable to passing to .NET's formatting
-	% functions.
-	% XXX this code is not yet complete.  We need to do a lot more work
-	% to make this work perfectly.
-:- func make_format_dotnet(list(char), maybe(list(char)),
-		maybe(list(char)), string, string) = string.

-make_format_dotnet(_Flags, MaybeWidth, MaybePrec, _LengthMod, Spec0) = String :-
-	(
-		MaybeWidth = yes(Width0),
-		Width = [',' | Width0]
+	%
+	% Representing a floating point number in scientific notation requires a base
+	% and an exponent. This function returns the base. But it is unsafe, since
+	% particular input result in the base having a mantissa with more than one digit.
+	% Therefore, the calling function must check for this problem.
+	%
+:- func calculate_base_unsafe(string, int) = string.
+calculate_base_unsafe(Float, Prec) = Exp :-
+	Place = decimal_pos(Float),
+	MantAndFrac = string__words(is_decimal_point, Float),
+	list__index0_det(MantAndFrac, 0, MantissaStr),
+	list__index0_det(MantAndFrac, 1, FractionStr),
+	( Place < 0
+	->
+		DecimalPos = abs(Place),
+		PaddedMantissaStr = string__substring(FractionStr, 0, DecimalPos),
+			%
+			% get rid of superfluous zeros.
+			%
+		MantissaInt = string__det_to_int(PaddedMantissaStr),
+		ExpMantissaStr = string__int_to_string(MantissaInt),
+			%
+			% create fractional part
+			%
+		PaddedFractionStr = string__pad_right(FractionStr, '0', Prec+1),
+		ExpFractionStr = string__substring(PaddedFractionStr, DecimalPos, Prec+1)
+	; Place > 0
+	->
+		ExpMantissaStr = string__substring(MantissaStr, 0, 1),
+		FirstHalfOfFractionStr = string__substring(MantissaStr, 1, Place),
+		ExpFractionStr = string__append(FirstHalfOfFractionStr, FractionStr)
 	;
-		MaybeWidth = no,
-		Width = []
+		ExpMantissaStr = MantissaStr,
+		ExpFractionStr = FractionStr
 	),
-	(
-		MaybePrec = yes(Prec)
+	MantissaAndPoint = string__append(ExpMantissaStr, "."),
+	UnroundedExpStr = string__append(MantissaAndPoint, ExpFractionStr),
+	Exp = change_precision(Prec, UnroundedExpStr).
+
+
+	%
+	% Determine the location of the decimal point in the string that represents a
+	% floating point number.
+	%
+:- func decimal_pos(string) = int.
+decimal_pos(Float) = Pos :-
+	MantAndFrac = string__words(is_decimal_point, Float),
+	list__index0_det(MantAndFrac, 0, MantissaStr),
+	NumZeros = string__length(MantissaStr) - 1,
+	Pos = find_non_zero_pos(string__to_char_list(Float), NumZeros).
+
+
+	%
+	% Given a list of chars representing a floating point number, function determines the
+	% the first position containing a non-zero digit. Positions after the decimal
+	% point are negative, and those before the decimal point are positive.
+	%
+:- func find_non_zero_pos(list(char), int) = int.
+find_non_zero_pos( Xs, CurrentPos) = ActualPos :-
+	( Xs = [Y|Ys]
+	->
+		( is_decimal_point(Y)
+		->
+		  	ActualPos = find_non_zero_pos(Ys, CurrentPos)
+		; Y = '0'
+		->
+		 	ActualPos = find_non_zero_pos(Ys, CurrentPos - 1)
+		;
+			ActualPos = CurrentPos
+		)
+	;
+		ActualPos = 0
+	).
+
+
+	%
+	% Change the precision of a float to a specified number of decimal places.
+	%
+	% n.b. OldFloat must be positive for this function to work.
+	%
+:- func change_precision(int, string) = string.
+change_precision(Prec, OldFloat) = NewFloat :-
+	MantAndFrac = string__words(is_decimal_point, OldFloat),
+	list__index0_det(MantAndFrac, 0, MantissaStr),
+	list__index0_det(MantAndFrac, 1, FractionStr),
+	FracStrLen = string__length(FractionStr),
+	( Prec > FracStrLen
+	->
+		PrecFracStr = string__pad_right(FractionStr, '0', Prec),
+		PrecMantissaStr = MantissaStr
+	;
+	Prec < FracStrLen
+	->
+		UnroundedFrac = string__substring(FractionStr, 0, Prec),
+		NextDigit = string__index_det(FractionStr, Prec),
+		( UnroundedFrac \= "",
+		  (char__to_int(NextDigit) - char__to_int('0')) >= 5
+		->
+			NewPrecFrac = string__det_to_int(UnroundedFrac) + 1,
+			NewPrecFracStrNotOK = string__int_to_string(NewPrecFrac),
+			NewPrecFracStr = string__pad_left(NewPrecFracStrNotOK, '0', Prec),
+			( string__length(NewPrecFracStr) > string__length(UnroundedFrac)
+			->
+				PrecFracStr = string__substring(NewPrecFracStr, 1, Prec),
+				PrecMantissaInt = string__det_to_int(MantissaStr) + 1,
+				PrecMantissaStr = string__int_to_string(PrecMantissaInt)
+			;
+				PrecFracStr = NewPrecFracStr,
+				PrecMantissaStr = MantissaStr
+			)
+
+		; UnroundedFrac = "",
+		  (char__to_int(NextDigit) - char__to_int('0')) >= 5
+		->
+			PrecMantissaInt = string__det_to_int(MantissaStr) + 1,
+			PrecMantissaStr = string__int_to_string(PrecMantissaInt),
+			PrecFracStr = ""
+		;
+			PrecFracStr = UnroundedFrac,
+			PrecMantissaStr = MantissaStr
+		)
 	;
-		MaybePrec = no,
-		Prec = []
+		PrecFracStr = FractionStr,
+		PrecMantissaStr = MantissaStr
 	),
-	( 	Spec0 = "i" -> Spec = "d"
-	;	Spec0 = "f" -> Spec = "e"
-	;	Spec = Spec0
-	),
-	String = string__append_list([
-		"{0",
-		from_char_list(Width),
-		":",
-		Spec,
-		from_char_list(Prec),
-%		LengthMod,
-%		from_char_list(Flags),
-		"}"]).
+	HalfNewFloat = string__append(PrecMantissaStr, "."),
+	NewFloat = string__append(HalfNewFloat, PrecFracStr).

+:- pred is_decimal_point(char :: in) is semidet.
+is_decimal_point('.').

-:- func int_length_modifer = string.
-:- pragma foreign_proc("C",
-	int_length_modifer = (LengthModifier::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	MR_make_aligned_string(LengthModifier,
-		(MR_String) (MR_Word) MR_INTEGER_LENGTH_MODIFIER);
-}").
+:- pred is_exponent(char :: in) is semidet.
+is_exponent('e').

-:- pragma foreign_proc("C#",
-	int_length_modifer = (LengthModifier::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	LengthModifier = """";
-}").

+	%
+	% Unlike the standard library function, this function converts a float to a string
+	% without resorting to scientific notation.
+	%
+:- func convert_float_to_string(float) = string.
+convert_float_to_string(Float) = String :-
+	FloatStr = string__float_to_string(Float),
+		%
+		% check for scientific representation.
+		%
+	( string__contains_char(FloatStr, 'e')
+	->
+		FloatAndExp = string__words(is_exponent, FloatStr),
+		list__index0_det(FloatAndExp, 0, FloatPtStr),
+		list__index0_det(FloatAndExp, 1, ExpStr),
+			%
+			% extract mantissa and fractional part
+			%
+		MantAndFrac = string__words(is_decimal_point, FloatPtStr),
+		list__index0_det(MantAndFrac, 0, MantissaStr),
+		list__index0_det(MantAndFrac, 1, FractionStr),
+			%
+			% what is the exponent?
+			%
+		ExpInt = string__det_to_int(ExpStr),
+		( ExpInt >= 0
+		->
+				%
+				% move decimal pt to the right.
+				%
+			ExtraDigits = ExpInt,
+			PaddedFracStr = string__pad_right(FractionStr, '0', ExtraDigits),
+			string__split(PaddedFracStr, ExtraDigits, MantissaRest, NewFraction),
+			NewMantissa = append(MantissaStr, MantissaRest),
+			MantAndPoint = append(NewMantissa, "."),
+			( NewFraction = ""
+			->
+				String = append(MantAndPoint, "0")
+			;
+				String = append(MantAndPoint, NewFraction)
+			)
+		;
+				%
+				% move decimal pt to the left.
+				%
+			ExtraDigits = abs(ExpInt),
+			PaddedMantissaStr = string__pad_left(MantissaStr, '0', ExtraDigits),
+			string__split(PaddedMantissaStr, length(PaddedMantissaStr) - ExtraDigits ,
+					NewMantissa, FractionRest),
+			(NewMantissa = ""
+			->
+				MantAndPoint = "0."
+			;
+				MantAndPoint = string__append(NewMantissa, ".")
+			),
+			String = append(MantAndPoint, append(FractionRest, FractionStr))
+		)
+	;
+		String = FloatStr
+	).
+
+
+	%
+	% Convert an integer to an octal string.
+	%
+:- func to_octal(integer) = string.
+to_octal(Num) = NumStr :-
+	( Num > integer(0)
+	->
+		Rest = to_octal(Num // integer(8)),
+		Rem = Num rem integer(8),
+		RemStr = integer__to_string(Rem),
+		NumStr = append(Rest, RemStr)
+	;
+		NumStr = ""
+	).
+
+
+	%
+	% Convert an integer to a hexadecimal string using a-f.
+	%
+:- func to_hex(integer) = string.
+to_hex(Num) = NumStr :-
+	( Num > integer(0)
+	->
+		Rest = to_hex( Num // integer(16)),
+		Rem = Num rem integer(16),
+		RemStr = get_hex_int(Rem),
+		NumStr = append(Rest, RemStr)
+	;
+		NumStr = ""
+	).
+
+
+	%
+	% Convert an integer to a hexadecimal string using A-F.
+	%
+:- func to_capital_hex(integer) = string.
+to_capital_hex(Num) = NumStr :-
+	( Num > integer(0)
+	->
+		Rest = to_capital_hex( Num//integer(16)),
+		Rem = Num rem integer(16),
+		RemStr = get_capital_hex_int(Rem),
+		NumStr = append(Rest, RemStr)
+	;
+		NumStr = ""
+	).
+
+
+	%
+	% Given a decimal integer, return the hexadecimal equivalent (using a-f).
+	%
+:- func get_hex_int(integer) = string.
+get_hex_int(Int) = HexStr :-
+	( 	Int < integer(10) -> HexStr = integer__to_string(Int)
+	;
+		Int = integer(10) -> HexStr = "a"
+	;
+		Int = integer(11) -> HexStr = "b"
+	;
+		Int = integer(12) -> HexStr = "c"
+	;
+		Int = integer(13) -> HexStr = "d"
+	;
+		Int = integer(14) -> HexStr = "e"
+	;
+		HexStr = "f"
+	).
+
+	%
+	% Convert an integer to a hexadecimal string using A-F.
+	%
+:- func get_capital_hex_int(integer) = string.
+get_capital_hex_int(Int) = HexStr :-
+	( 	Int < integer(10) -> HexStr = integer__to_string(Int)
+	;
+		Int = integer(10) -> HexStr = "A"
+	;
+		Int = integer(11) -> HexStr = "B"
+	;
+		Int = integer(12) -> HexStr = "C"
+	;
+		Int = integer(13) -> HexStr = "D"
+	;
+		Int = integer(14) -> HexStr = "E"
+	;
+		HexStr = "F"
+	).
+
+
+
+	%
+	% Given a string and set of flags, this function will either left or right justify
+	% the string.
+	%
+:- func justify_string(list(char), maybe(int), string) = string.
+justify_string(Flags, Width, OldStr) = JustifiedStr :-
+	( Width = yes(FWidth),
+	  FWidth > string__length(OldStr)
+	->
+		( member('-', Flags)  % do we left (or right) justify?
+		->
+			JustifiedStr = string__pad_right(OldStr, ' ', FWidth)
+		;
+			JustifiedStr = string__pad_left(OldStr, ' ', FWidth)
+		)
+	;
+		JustifiedStr = OldStr
+	).

-	% Create a string from a float using the format string.
-	% Note it is the responsibility of the caller to ensure that the
-	% format string is valid.
-:- func format_float(string, float) = string.
-:- pragma foreign_proc("C",
-	format_float(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	MR_save_transient_hp();
-	Str = MR_make_string(MR_PROC_LABEL, FormatStr, (double) Val);
-	MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
-	format_float(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	Str = System.String.Format(FormatStr, Val);
-}").

-	% Create a string from a int using the format string.
-	% Note it is the responsibility of the caller to ensure that the
-	% format string is valid.
-:- func format_int(string, int) = string.
-:- pragma foreign_proc("C",
-	format_int(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	MR_save_transient_hp();
-	Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
-	MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
-	format_int(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	Str = System.String.Format(FormatStr, Val);
-}").

-	% Create a string from a string using the format string.
-	% Note it is the responsibility of the caller to ensure that the
-	% format string is valid.
-:- func format_string(string, string) = string.
-:- pragma foreign_proc("C",
-	format_string(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
-}").
-:- pragma foreign_proc("C#",
-	format_string(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	Str = System.String.Format(FormatStr, Val);
-}").

-	% Create a string from a char using the format string.
-	% Note it is the responsibility of the caller to ensure that the
-	% format string is valid.
-:- func format_char(string, char) = string.
-:- pragma foreign_proc("C",
-	format_char(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	MR_save_transient_hp();
-	Str = MR_make_string(MR_PROC_LABEL, FormatStr, Val);
-	MR_restore_transient_hp();
-}").
-:- pragma foreign_proc("C#",
-	format_char(FormatStr::in, Val::in) = (Str::out),
-		[will_not_call_mercury, promise_pure, thread_safe], "{
-	Str = System.String.Format(FormatStr, Val);
-}").


 %-----------------------------------------------------------------------------%
@@ -2539,7 +3223,7 @@
 		preceding_boundary(SepP, String, I - 1)
 	).

-% ---------------------------------------------------------------------------- %
+% ---------------------------------------------------------------------------- v%

 S1 ++ S2 = string__append(S1, S2).


--------------------------------------------------------------------------
mercury-reviews mailing list
post:  mercury-reviews at cs.mu.oz.au
administrative address: owner-mercury-reviews at cs.mu.oz.au
unsubscribe: Address: mercury-reviews-request at cs.mu.oz.au Message: unsubscribe
subscribe:   Address: mercury-reviews-request at cs.mu.oz.au Message: subscribe
--------------------------------------------------------------------------



More information about the reviews mailing list