1709 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1709 lines
		
	
	
		
			38 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*  Part of SWI-Prolog
 | |
| 
 | |
|     Author:        Jan Wielemaker
 | |
|     E-mail:        J.Wielemaker@vu.nl
 | |
|     WWW:           http://www.swi-prolog.org
 | |
|     Copyright (C): 1985-2013, University of Amsterdam
 | |
| 			      VU University Amsterdam
 | |
| 
 | |
|     This library is free software; you can redistribute it and/or
 | |
|     modify it under the terms of the GNU Lesser General Public
 | |
|     License as published by the Free Software Foundation; either
 | |
|     version 2.1 of the License, or (at your option) any later version.
 | |
| 
 | |
|     This library is distributed in the hope that it will be useful,
 | |
|     but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|     Lesser General Public License for more details.
 | |
| 
 | |
|     You should have received a copy of the GNU Lesser General Public
 | |
|     License along with this library; if not, write to the Free Software
 | |
|     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 | |
| */
 | |
| 
 | |
| /**
 | |
|  * @defgroup Format Formatted Output
 | |
|  * @ingroup InputOutput
 | |
|  * @{
 | |
|  */
 | |
| 
 | |
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| Formatted output (Prolog predicates format/[1,2,3]).   One  day,  the  C
 | |
| source should also use format() to produce error messages, etc.
 | |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | |
| 
 | |
| #include "pl-incl.h"
 | |
| #include "pl-ctype.h"
 | |
| #include "pl-utf8.h"
 | |
| #include <ctype.h>
 | |
| 
 | |
| static char *	formatInteger(PL_locale *locale, int div, int radix,
 | |
| 			     bool smll, Number n, Buffer out);
 | |
| static char *	formatFloat(PL_locale *locale, int how, int arg,
 | |
| 			    Number f, Buffer out);
 | |
| 
 | |
| #define MAXRUBBER 100
 | |
| 
 | |
| struct rubber
 | |
| { size_t where;				/* where is rubber in output */
 | |
|   size_t size;				/* how big should it be */
 | |
|   pl_wchar_t pad;			/* padding character */
 | |
| };
 | |
| 
 | |
| typedef struct
 | |
| { IOSTREAM *out;			/* our output stream */
 | |
|   int column;				/* current column */
 | |
|   tmp_buffer buffer;			/* bin for characters with tabs */
 | |
|   size_t buffered;			/* characters in buffer */
 | |
|   int pending_rubber;			/* number of not-filled ~t's */
 | |
|   struct rubber rub[MAXRUBBER];
 | |
| } format_state;
 | |
| 
 | |
| #define BUFSIZE		1024
 | |
| #define DEFAULT		(-1)
 | |
| #define SHIFT		{ argc--; argv++; }
 | |
| #define NEED_ARG	{ if ( argc <= 0 ) \
 | |
| 			  { FMT_ERROR("not enough arguments"); \
 | |
| 			  } \
 | |
| 			}
 | |
| #define FMT_ERROR(fmt)	return (void)Sunlock(fd), \
 | |
| 			  PL_error(NULL, 0, NULL, ERR_FORMAT, fmt)
 | |
| #define FMT_ARG(c, a)	return (void)Sunlock(fd), \
 | |
| 			       PL_error(NULL, 0, NULL, \
 | |
| 					ERR_FORMAT_ARG, c, a)
 | |
| #define FMT_EXEPTION()	return (void)Sunlock(fd), FALSE
 | |
| 
 | |
| 
 | |
| static PL_locale prolog_locale =
 | |
| { 0,0,LOCALE_MAGIC,1,
 | |
|   L".", NULL
 | |
| };
 | |
| 
 | |
| 
 | |
| static int
 | |
| update_column(int col, int c)
 | |
| { switch(c)
 | |
|   { case '\n':	return 0;
 | |
|     case '\r':  return 0;
 | |
|     case '\t':	return (col + 1) | 0x7;
 | |
|     case '\b':	return (col <= 0 ? 0 : col - 1);
 | |
|     default:	return col + 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| Low-level output. If there is pending  rubber   the  output is stored in
 | |
| UTF-8 format in the state's `buffer'.   The  `buffered' field represents
 | |
| the number of UTF-8 characters in the buffer.
 | |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | |
| 
 | |
| static int
 | |
| outchr(format_state *state, int chr)
 | |
| { if ( state->pending_rubber )
 | |
|   { if ( chr > 0x7f )
 | |
|     { char buf[8];
 | |
|       char *s, *e;
 | |
| 
 | |
|       e = utf8_put_char(buf, chr);
 | |
|       for(s=buf; s<e; s++)
 | |
| 	addBuffer((Buffer)&state->buffer, *s, char);
 | |
|     } else
 | |
|     { char c = chr;
 | |
| 
 | |
|       addBuffer((Buffer)&state->buffer, c, char);
 | |
|     }
 | |
| 
 | |
|     state->buffered++;
 | |
|   } else
 | |
|   { if ( Sputcode(chr, state->out) < 0 )
 | |
|       return FALSE;
 | |
|   }
 | |
| 
 | |
|   state->column = update_column(state->column, chr);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| Emit ASCII 0-terminated strings resulting from sprintf() on numeric
 | |
| arguments.  No fuzz with wide characters here.
 | |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | |
| 
 | |
| static int
 | |
| outstring(format_state *state, const char *s, size_t len)
 | |
| { const char *q;
 | |
|   const char *e = &s[len];
 | |
| 
 | |
|   if ( state->pending_rubber )
 | |
|   { addMultipleBuffer(&state->buffer, s, len, char);
 | |
|     state->buffered += len;
 | |
|   } else
 | |
|   { for(q=s; q < e; q++)
 | |
|     { if ( Sputcode(*q&0xff, state->out) < 0 )
 | |
| 	return FALSE;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for(q=s; q < e; q++)
 | |
|     state->column = update_column(state->column, *q&0xff);
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| oututf8(format_state *state, const char *s, size_t len)
 | |
| { const char *e = &s[len];
 | |
| 
 | |
|   while(s<e)
 | |
|   { int chr;
 | |
| 
 | |
|     s = utf8_get_char(s, &chr);
 | |
|     if ( !outchr(state, chr) )
 | |
|       return FALSE;
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| oututf80(format_state *state, const char *s)
 | |
| { return oututf8(state, s, strlen(s));
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| outtext(format_state *state, PL_chars_t *txt)
 | |
| { switch(txt->encoding)
 | |
|   { case ENC_ISO_LATIN_1:
 | |
|       return outstring(state, txt->text.t, txt->length);
 | |
|     case ENC_UTF8:
 | |
|       return oututf8(state, txt->text.t, txt->length);
 | |
|     case ENC_WCHAR:
 | |
|     { const pl_wchar_t *s = txt->text.w;
 | |
|       const pl_wchar_t *e = &s[txt->length];
 | |
| 
 | |
|       while(s<e)
 | |
|       { if ( !outchr(state, *s++) )
 | |
| 	  return FALSE;
 | |
|       }
 | |
| 
 | |
|       return TRUE;
 | |
|     }
 | |
|     default:
 | |
|     { assert(0);
 | |
|       return FALSE;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| #define format_predicates (GD->format.predicates)
 | |
| 
 | |
| static int	update_column(int, Char);
 | |
| static bool	do_format(IOSTREAM *fd, PL_chars_t *fmt,
 | |
| 			  int ac, term_t av, Module m);
 | |
| static void	distribute_rubber(struct rubber *, int, int);
 | |
| static int	emit_rubber(format_state *state);
 | |
| 
 | |
| 
 | |
| 		/********************************
 | |
| 		*       PROLOG CONNECTION	*
 | |
| 		********************************/
 | |
| 
 | |
| word
 | |
| pl_format_predicate(term_t chr, term_t descr)
 | |
| { int c;
 | |
|   predicate_t proc = NULL;
 | |
|   Symbol s;
 | |
|   int arity;
 | |
| 
 | |
|   if ( !PL_get_char_ex(chr, &c, FALSE) )
 | |
|     fail;
 | |
| 
 | |
|   if ( !get_procedure(descr, &proc, 0, GP_CREATE) )
 | |
|     fail;
 | |
|   PL_predicate_info(proc, NULL, &arity, NULL);
 | |
|   if ( arity == 0 )
 | |
|     return PL_error(NULL, 0, "arity must be > 0", ERR_DOMAIN,
 | |
| 		    PL_new_atom("format_predicate"),
 | |
| 		    descr);
 | |
| 
 | |
|   if ( !format_predicates )
 | |
|     format_predicates = newHTable(8);
 | |
| 
 | |
|   if ( (s = lookupHTable(format_predicates, (void *)(intptr_t)c)) )
 | |
|     s->value = proc;
 | |
|   else
 | |
|     addHTable(format_predicates, (void *)(intptr_t)c, proc);
 | |
| 
 | |
|   succeed;
 | |
| }
 | |
| 
 | |
| 
 | |
| word
 | |
| pl_current_format_predicate(term_t chr, term_t descr, control_t h)
 | |
| { GET_LD
 | |
|   Symbol s = NULL;
 | |
|   TableEnum e;
 | |
|   fid_t fid;
 | |
| 
 | |
|   switch( ForeignControl(h) )
 | |
|   { case FRG_FIRST_CALL:
 | |
|       if ( !format_predicates )
 | |
| 	fail;
 | |
|       e = newTableEnum(format_predicates);
 | |
|       break;
 | |
|     case FRG_REDO:
 | |
|       e = ForeignContextPtr(h);
 | |
|       break;
 | |
|     case FRG_CUTTED:
 | |
|       e = ForeignContextPtr(h);
 | |
|       freeTableEnum(e);
 | |
|     default:
 | |
|       succeed;
 | |
|   }
 | |
| 
 | |
|   if ( !(fid = PL_open_foreign_frame()) )
 | |
|   { freeTableEnum(e);
 | |
|     return FALSE;
 | |
|   }
 | |
|   while( (s=advanceTableEnum(e)) )
 | |
|   { if ( PL_unify_integer(chr, (intptr_t)s->name) &&
 | |
| 	 PL_unify_predicate(descr, (predicate_t)s->value, 0) )
 | |
|     { PL_close_foreign_frame(fid);
 | |
|       ForeignRedoPtr(e);
 | |
|     }
 | |
| 
 | |
|     PL_rewind_foreign_frame(fid);
 | |
|   }
 | |
| 
 | |
|   PL_close_foreign_frame(fid);
 | |
|   freeTableEnum(e);
 | |
|   fail;
 | |
| }
 | |
| 
 | |
| 
 | |
| static word
 | |
| format_impl(IOSTREAM *out, term_t format, term_t Args, Module m)
 | |
| { GET_LD
 | |
|   term_t argv;
 | |
|   int argc = 0;
 | |
|   term_t args = PL_copy_term_ref(Args);
 | |
|   int rval;
 | |
|   PL_chars_t fmt;
 | |
| 
 | |
|   if ( !PL_get_text(format, &fmt, CVT_ALL|BUF_RING) )
 | |
|     return PL_error("format", 3, NULL, ERR_TYPE, ATOM_text, format);
 | |
| 
 | |
|   if ( (argc = (int)lengthList(args, FALSE)) >= 0 )
 | |
|   { term_t head = PL_new_term_ref();
 | |
|     int n = 0;
 | |
| 
 | |
|     argv = PL_new_term_refs(argc);
 | |
|     while( PL_get_list(args, head, args) )
 | |
|       PL_put_term(argv+n++, head);
 | |
|   } else
 | |
|   { argc = 1;
 | |
|     argv = PL_new_term_refs(argc);
 | |
| 
 | |
|     PL_put_term(argv, args);
 | |
|   }
 | |
| 
 | |
|   startCritical;
 | |
|   switch(fmt.storage)			/* format can do call-back! */
 | |
|   { case PL_CHARS_RING:
 | |
|     case PL_CHARS_STACK:
 | |
|       PL_save_text(&fmt, BUF_MALLOC);
 | |
|       break;
 | |
|     default:
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   rval = do_format(out, &fmt, argc, argv, m);
 | |
|   PL_free_text(&fmt);
 | |
|   if ( !endCritical )
 | |
|     return FALSE;
 | |
| 
 | |
|   return rval;
 | |
| }
 | |
| 
 | |
| 
 | |
| /** @pred  format(+ _S_,+ _T_,+ _L_)
 | |
| 
 | |
| Print formatted output to stream  _S_.
 | |
| 
 | |
| 
 | |
| */
 | |
| word
 | |
| pl_format3(term_t out, term_t format, term_t args)
 | |
| { GET_LD
 | |
|   redir_context ctx;
 | |
|   word rc;
 | |
|   Module m = NULL;
 | |
|   term_t list = PL_new_term_ref();
 | |
| 
 | |
|   if ( !PL_strip_module(args, &m, list) )
 | |
|     return FALSE;
 | |
| 
 | |
|   if ( (rc=setupOutputRedirect(out, &ctx, FALSE)) )
 | |
|   { if ( (rc = format_impl(ctx.stream, format, list, m)) )
 | |
|       rc = closeOutputRedirect(&ctx);
 | |
|     else
 | |
|       discardOutputRedirect(&ctx);
 | |
|   }
 | |
| 
 | |
|   return rc;
 | |
| }
 | |
| 
 | |
| /** @pred  format(+ _T_,+ _L_)
 | |
| 
 | |
| 
 | |
| Print formatted output to the current output stream. The arguments in
 | |
| list  _L_ are output according to the string or atom  _T_.
 | |
| 
 | |
| A control sequence is introduced by a `w`. The following control
 | |
| sequences are available in YAP:
 | |
| 
 | |
| 
 | |
| 
 | |
| + `~~`
 | |
| Print a single tilde.
 | |
| 
 | |
| + `~a`
 | |
| The next argument must be an atom, that will be printed as if by `write`.
 | |
| 
 | |
| + `~Nc`
 | |
| The next argument must be an integer, that will be printed as a
 | |
| character code. The number  _N_ is the number of times to print the
 | |
| character (default 1).
 | |
| 
 | |
| + `~Ne`
 | |
| + `~NE`
 | |
| + `~Nf`
 | |
| + `~Ng`
 | |
| + `~NG`
 | |
| The next argument must be a floating point number. The float  _F_, the number
 | |
|  _N_ and the control code `c` will be passed to `printf` as:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
|     printf("%s.Nc", F)
 | |
| ~~~~~
 | |
| 
 | |
| As an example:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("~8e, ~8E, ~8f, ~8g, ~8G~w",
 | |
|           [3.14,3.14,3.14,3.14,3.14,3.14]).
 | |
| 3.140000e+00, 3.140000E+00, 3.140000, 3.14, 3.143.14
 | |
| ~~~~~
 | |
| 
 | |
| + `~Nd`
 | |
| The next argument must be an integer, and  _N_ is the number of digits
 | |
| after the decimal point. If  _N_ is `0` no decimal points will be
 | |
| printed. The default is  _N = 0_.
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("~2d, ~d",[15000, 15000]).
 | |
| 150.00, 15000
 | |
| ~~~~~
 | |
| 
 | |
| + `~ND`
 | |
| Identical to `~Nd`, except that commas are used to separate groups
 | |
| of three digits.
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("~2D, ~D",[150000, 150000]).
 | |
| 1,500.00, 150,000
 | |
| ~~~~~
 | |
| 
 | |
| + `~i`
 | |
| Ignore the next argument in the list of arguments:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format('The ~i met the boregrove',[mimsy]).
 | |
| The  met the boregrove
 | |
| ~~~~~
 | |
| 
 | |
| + `~k`
 | |
| Print the next argument with `write_canonical`:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("Good night ~k",a+[1,2]).
 | |
| Good night +(a,[1,2])
 | |
| ~~~~~
 | |
| 
 | |
| + `~Nn`
 | |
| Print  _N_ newlines (where  _N_ defaults to 1).
 | |
| 
 | |
| + `~NN`
 | |
| Print  _N_ newlines if at the beginning of the line (where  _N_
 | |
| defaults to 1).
 | |
| 
 | |
| + `~Nr`
 | |
| The next argument must be an integer, and  _N_ is interpreted as a
 | |
| radix, such that `2 <= N <= 36` (the default is 8).
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("~2r, 0x~16r, ~r",
 | |
|           [150000, 150000, 150000]).
 | |
| 100100100111110000, 0x249f0, 444760
 | |
| ~~~~~
 | |
| 
 | |
| Note that the letters `a-z` denote digits larger than 9.
 | |
| 
 | |
| + `~NR`
 | |
| Similar to `~NR`. The next argument must be an integer, and  _N_ is
 | |
| interpreted as a radix, such that `2 <= N <= 36` (the default is 8).
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("~2r, 0x~16r, ~r",
 | |
|           [150000, 150000, 150000]).
 | |
| 100100100111110000, 0x249F0, 444760
 | |
| ~~~~~
 | |
| 
 | |
| The only difference is that letters `A-Z` denote digits larger than 9.
 | |
| 
 | |
| + `~p`
 | |
| Print the next argument with print/1:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("Good night ~p",a+[1,2]).
 | |
| Good night a+[1,2]
 | |
| ~~~~~
 | |
| 
 | |
| + `~q`
 | |
| Print the next argument with writeq/1:
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("Good night ~q",'Hello'+[1,2]).
 | |
| Good night 'Hello'+[1,2]
 | |
| ~~~~~
 | |
| 
 | |
| + `~Ns`
 | |
| The next argument must be a list of character codes. The system then
 | |
| outputs their representation as a string, where  _N_ is the maximum
 | |
| number of characters for the string ( _N_ defaults to the length of the
 | |
| string).
 | |
| 
 | |
| ~~~~~{.prolog}
 | |
| ?- format("The ~s are ~4s",["woods","lovely"]).
 | |
| The woods are love
 | |
| ~~~~~
 | |
| 
 | |
| + `~w`
 | |
| Print the next argument with write/1:
 | |
| 
 | |
| ~~~~~
 | |
| ?- format("Good night ~w",'Hello'+[1,2]).
 | |
| Good night Hello+[1,2]
 | |
| ~~~~~
 | |
| 
 | |
| 
 | |
| The number of arguments, `N`, may be given as an integer, or it
 | |
| may be given as an extra argument. The next example shows a small
 | |
| procedure to write a variable number of `a` characters:
 | |
| 
 | |
| ~~~~~
 | |
| write_many_as(N) :-
 | |
|         format("~*c",[N,0'a]).
 | |
| ~~~~~
 | |
| 
 | |
| The format/2 built-in also allows for formatted output.  One can
 | |
| specify column boundaries and fill the intermediate space by a padding
 | |
| character:
 | |
| 
 | |
| + `~N|`
 | |
| Set a column boundary at position  _N_, where  _N_ defaults to the
 | |
| current position.
 | |
| 
 | |
| + `~N+`
 | |
| Set a column boundary at  _N_ characters past the current position, where
 | |
|  _N_ defaults to `8`.
 | |
| 
 | |
| + `~Nt`
 | |
| Set padding for a column, where  _N_ is the fill code (default is
 | |
| `SPC`).
 | |
| 
 | |
| 
 | |
| 
 | |
| The next example shows how to align columns and padding. We first show
 | |
| left-alignment:
 | |
| 
 | |
| ~~~~~
 | |
|    ?- format("~n*Hello~16+*~n",[]).
 | |
| *Hello          *
 | |
| ~~~~~
 | |
| 
 | |
| Note that we reserve 16 characters for the column.
 | |
| 
 | |
| The following example shows how to do right-alignment:
 | |
| 
 | |
| ~~~~~
 | |
|    ?- format("*~tHello~16+*~n",[]).
 | |
| *          Hello*
 | |
| 
 | |
| ~~~~~
 | |
| 
 | |
| The `~t` escape sequence forces filling before `Hello`.
 | |
| 
 | |
| We next show how to do centering:
 | |
| 
 | |
| ~~~~~
 | |
|    ?- format("*~tHello~t~16+*~n",[]).
 | |
| *     Hello     *
 | |
| ~~~~~
 | |
| 
 | |
| The two `~t` escape sequence force filling both before and after
 | |
| `Hello`. Space is then evenly divided between the right and the
 | |
| left sides.
 | |
| 
 | |
| 
 | |
| */
 | |
| word
 | |
| pl_format(term_t fmt, term_t args)
 | |
| { return pl_format3(0, fmt, args);
 | |
| }
 | |
| 
 | |
| 
 | |
| static inline int
 | |
| get_chr_from_text(const PL_chars_t *t, int index)
 | |
| { switch(t->encoding)
 | |
|   { case ENC_ISO_LATIN_1:
 | |
|       return t->text.t[index]&0xff;
 | |
|     case ENC_WCHAR:
 | |
|       return t->text.w[index];
 | |
|     case ENC_UTF8:
 | |
|       return t->text.w[index];
 | |
|     default:
 | |
|       assert(0);
 | |
|       return 0;				/* not reached */
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| 		/********************************
 | |
| 		*       ACTUAL FORMATTING	*
 | |
| 		********************************/
 | |
| 
 | |
| static bool
 | |
| do_format(IOSTREAM *fd, PL_chars_t *fmt, int argc, term_t argv, Module m)
 | |
| { GET_LD
 | |
|   format_state state;			/* complete state */
 | |
|   int tab_stop = 0;			/* padded tab stop */
 | |
|   Symbol s;
 | |
|   unsigned int here = 0;
 | |
|   int rc = TRUE;
 | |
| 
 | |
|   Slock(fd);				/* buffer locally */
 | |
| 
 | |
|   state.out = fd;
 | |
|   state.pending_rubber = 0;
 | |
|   initBuffer(&state.buffer);
 | |
|   state.buffered = 0;
 | |
| 
 | |
|   if ( fd->position )
 | |
|     state.column = fd->position->linepos;
 | |
|   else
 | |
|     state.column = 0;
 | |
| 
 | |
|   while(here < fmt->length)
 | |
|   { int c = get_chr_from_text(fmt, here);
 | |
| 
 | |
|     switch(c)
 | |
|     { case '~':
 | |
| 	{ int arg = DEFAULT;		/* Numeric argument */
 | |
| 	  int mod_colon = FALSE;	/* Used colon modifier */
 | |
| 					/* Get the numeric argument */
 | |
| 	  c = get_chr_from_text(fmt, ++here);
 | |
| 
 | |
| 	  if ( isDigitW(c) )
 | |
| 	  { arg = c - '0';
 | |
| 
 | |
| 	    here++;
 | |
| 	    while(here < fmt->length)
 | |
| 	    { c = get_chr_from_text(fmt, here);
 | |
| 
 | |
| 	      if ( isDigitW(c) )
 | |
| 	      { int dw = c - '0';
 | |
| 		int arg2 = arg*10 + dw;
 | |
| 
 | |
| 		if ( (arg2 - dw)/10 != arg )	/* see mul64() in pl-arith.c */
 | |
| 		{ FMT_ERROR("argument overflow");
 | |
| 		}
 | |
| 		arg = arg2;
 | |
| 		here++;
 | |
| 	      } else
 | |
| 		break;
 | |
| 	    }
 | |
| 	  } else if ( c == '*' )
 | |
| 	  { NEED_ARG;
 | |
| 	    if ( PL_get_integer(argv, &arg) )
 | |
| 	    { SHIFT;
 | |
| 	    } else
 | |
| 	      FMT_ERROR("no or negative integer for `*' argument");
 | |
| 	    c = get_chr_from_text(fmt, ++here);
 | |
| 	  } else if ( c == '`' && here < fmt->length )
 | |
| 	  { arg = get_chr_from_text(fmt, ++here);
 | |
| 	    c = get_chr_from_text(fmt, ++here);
 | |
| 	  }
 | |
| 
 | |
| 	  if ( c == ':' )
 | |
| 	  { mod_colon = TRUE;
 | |
| 	    c = get_chr_from_text(fmt, ++here);
 | |
| 	  }
 | |
| 
 | |
| 					/* Check for user defined format */
 | |
| 	  if ( format_predicates &&
 | |
| 	       (s = lookupHTable(format_predicates, (void*)((intptr_t)c))) )
 | |
| 	  { predicate_t proc = (predicate_t) s->value;
 | |
| 	    int arity;
 | |
| 	    term_t av;
 | |
| 	    char buf[BUFSIZE];
 | |
| 	    char *str = buf;
 | |
| 	    size_t bufsize = BUFSIZE;
 | |
| 	    int i;
 | |
| 
 | |
| 	    PL_predicate_info(proc, NULL, &arity, NULL);
 | |
| 	    av = PL_new_term_refs(arity);
 | |
| 
 | |
| 	    if ( arg == DEFAULT )
 | |
| 	      PL_put_atom(av+0, ATOM_default);
 | |
| 	    else
 | |
| 	      PL_put_integer(av+0, arg);
 | |
| 
 | |
| 	    for(i=1; i < arity; i++)
 | |
| 	    { NEED_ARG;
 | |
| 	      PL_put_term(av+i, argv);
 | |
| 	      SHIFT;
 | |
| 	    }
 | |
| 
 | |
| 	    tellString(&str, &bufsize, ENC_UTF8);
 | |
| 	    rc = PL_call_predicate(NULL, PL_Q_PASS_EXCEPTION, proc, av);
 | |
| 	    toldString();
 | |
| 	    if ( !rc )
 | |
| 	    { if ( str != buf )
 | |
| 		free(str);
 | |
| 	      goto out;
 | |
| 	    }
 | |
| 	    oututf8(&state, str, bufsize);
 | |
| 	    if ( str != buf )
 | |
| 	      free(str);
 | |
| 
 | |
| 	    here++;
 | |
| 	  } else
 | |
| 	  { switch(c)			/* Build in formatting */
 | |
| 	    { case 'a':			/* atomic */
 | |
| 		{ PL_chars_t txt;
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( !PL_get_text(argv, &txt, CVT_ATOMIC) )
 | |
| 		    FMT_ARG("a", argv);
 | |
| 		  SHIFT;
 | |
| 		  rc = outtext(&state, &txt);
 | |
|                   if ( !rc )
 | |
| 		    goto out;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'c':			/* ~c: character code */
 | |
| 		{ int chr;
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( PL_get_integer(argv, &chr) && chr >= 0 )
 | |
| 		  { int times = (arg == DEFAULT ? 1 : arg);
 | |
| 
 | |
| 		    SHIFT;
 | |
| 		    while(times-- > 0)
 | |
| 		    { rc = outchr(&state, chr);
 | |
| 		      if ( !rc )
 | |
| 		        goto out;
 | |
| 		    }
 | |
| 		  } else
 | |
| 		    FMT_ARG("c", argv);
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'e':			/* exponential float */
 | |
| 	      case 'E':			/* Exponential float */
 | |
| 	      case 'f':			/* float */
 | |
| 	      case 'g':			/* shortest of 'f' and 'e' */
 | |
| 	      case 'G':			/* shortest of 'f' and 'E' */
 | |
| 		{ number n;
 | |
| 		  union {
 | |
| 		  tmp_buffer b;
 | |
| 		    buffer b1;
 | |
| 		  } u;
 | |
| 		  PL_locale *l;
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( !valueExpression(argv, &n PASS_LD) )
 | |
| 		  { char f[2];
 | |
| 
 | |
| 		    f[0] = c;
 | |
| 		    f[1] = EOS;
 | |
| 		    FMT_ARG(f, argv);
 | |
| 		  }
 | |
| 		  SHIFT;
 | |
| 
 | |
| 		  if ( c == 'f' && mod_colon )
 | |
| 		    l = fd->locale;
 | |
| 		  else
 | |
| 		    l = &prolog_locale;
 | |
| 
 | |
| 		  initBuffer(&u.b);
 | |
| 		  rc = formatFloat(l, c, arg, &n, &u.b1) != NULL;
 | |
| 		  clearNumber(&n);
 | |
| 		  if ( rc )
 | |
| 		    rc = oututf80(&state, baseBuffer(&u.b, char));
 | |
| 		  discardBuffer(&u.b);
 | |
|                   if ( !rc )
 | |
| 		    goto out;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'd':			/* integer */
 | |
| 	      case 'D':			/* grouped integer */
 | |
| 	      case 'r':			/* radix number */
 | |
| 	      case 'R':			/* Radix number */
 | |
| 	      case 'I':			/* Prolog 1_000_000 */
 | |
| 		{ number i;
 | |
| 		  tmp_buffer b;
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( !valueExpression(argv, &i PASS_LD) ||
 | |
| 		       !toIntegerNumber(&i, 0) )
 | |
| 		  { char f[2];
 | |
| 
 | |
| 		    f[0] = c;
 | |
| 		    f[1] = EOS;
 | |
| 		    FMT_ARG(f, argv);
 | |
| 		  }
 | |
| 		  SHIFT;
 | |
| 		  initBuffer(&b);
 | |
| 		  if ( c == 'd' || c == 'D' )
 | |
| 		  { PL_locale ltmp;
 | |
| 		    PL_locale *l;
 | |
| 		    static char grouping[] = {3,0};
 | |
| 
 | |
| 		    if ( c == 'D' )
 | |
| 		    { ltmp.thousands_sep = L",";
 | |
| 		      ltmp.decimal_point = L".";
 | |
| 		      ltmp.grouping = grouping;
 | |
| 		      l = <mp;
 | |
| 		    } else if ( mod_colon )
 | |
| 		    { l = fd->locale;
 | |
| 		    } else
 | |
| 		    { l = NULL;
 | |
| 		    }
 | |
| 
 | |
| 		    if ( arg == DEFAULT )
 | |
| 		      arg = 0;
 | |
| 		    if ( !formatInteger(l, arg, 10, TRUE, &i, (Buffer)&b) )
 | |
| 		      FMT_EXEPTION();
 | |
| 		  } else if ( c == 'I' )
 | |
| 		  { PL_locale ltmp;
 | |
| 		    char grouping[2];
 | |
| 
 | |
| 		    grouping[0] = (arg == DEFAULT ? 3 : arg);
 | |
| 		    grouping[1] = '\0';
 | |
| 		    ltmp.thousands_sep = L"_";
 | |
| 		    ltmp.grouping = grouping;
 | |
| 
 | |
| 		    if ( !formatInteger(<mp, 0, 10, TRUE, &i, (Buffer)&b) )
 | |
| 		      FMT_EXEPTION();
 | |
| 		  } else			/* r,R */
 | |
| 		  { if ( arg == DEFAULT )
 | |
| 		      FMT_ERROR("r,R requires radix specifier");
 | |
| 		    if ( arg < 1 || arg > 36 )
 | |
| 		    { term_t r = PL_new_term_ref();
 | |
| 
 | |
| 		      PL_put_integer(r, arg);
 | |
| 		      Sunlock(fd);
 | |
| 		      return PL_error(NULL, 0, NULL, ERR_DOMAIN,
 | |
| 				      ATOM_radix, r);
 | |
| 		    }
 | |
| 		    if ( !formatInteger(NULL, 0, arg, c == 'r', &i, (Buffer)&b) )
 | |
| 		      FMT_EXEPTION();
 | |
| 		  }
 | |
| 		  clearNumber(&i);
 | |
| 		  rc = oututf80(&state, baseBuffer(&b, char));
 | |
| 		  discardBuffer(&b);
 | |
| 		  if ( !rc )
 | |
| 		    goto out;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 's':			/* string */
 | |
| 		{ PL_chars_t txt;
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( !PL_get_text(argv, &txt, CVT_LIST|CVT_STRING) &&
 | |
| 		       !PL_get_text(argv, &txt, CVT_ATOM) ) /* SICStus compat */
 | |
| 		    FMT_ARG("s", argv);
 | |
| 		  rc = outtext(&state, &txt);
 | |
| 		  SHIFT;
 | |
| 		  if ( !rc )
 | |
| 		    goto out;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'i':			/* ignore */
 | |
| 		{ NEED_ARG;
 | |
| 		  SHIFT;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 		{ Func f;
 | |
| 		  char buf[BUFSIZE];
 | |
| 		  char *str;
 | |
| 
 | |
| 	      case 'k':			/* write_canonical */
 | |
| 		  f = pl_write_canonical;
 | |
| 	          goto pl_common;
 | |
| 	      case 'p':			/* print */
 | |
| 		  f = pl_print;
 | |
| 	          goto pl_common;
 | |
| 	      case 'q':			/* writeq */
 | |
| 		  f = pl_writeq;
 | |
| 	          goto pl_common;
 | |
| 	      case 'w':			/* write */
 | |
| 		  f = pl_write;
 | |
| 		  pl_common:
 | |
| 
 | |
| 		  NEED_ARG;
 | |
| 		  if ( state.pending_rubber )
 | |
| 		  { size_t bufsize = BUFSIZE;
 | |
| 
 | |
| 		    str = buf;
 | |
| 		    tellString(&str, &bufsize, ENC_UTF8);
 | |
| 		    rc = (*f)(argv);
 | |
| 		    toldString();
 | |
| 		    if ( !rc )
 | |
| 		      goto out;
 | |
| 		    oututf8(&state, str, bufsize);
 | |
| 		    if ( str != buf )
 | |
| 		      free(str);
 | |
| 		  } else
 | |
| 		  { if ( fd->position &&
 | |
| 			 fd->position->linepos == state.column )
 | |
| 		    { IOSTREAM *old = Scurout;
 | |
| 
 | |
| 		      Scurout = fd;
 | |
| 		      rc = (int)(*f)(argv);
 | |
| 		      Scurout = old;
 | |
| 		      if ( !rc )
 | |
| 			goto out;
 | |
| 
 | |
| 		      state.column = fd->position->linepos;
 | |
| 		    } else
 | |
| 		    { size_t bufsize = BUFSIZE;
 | |
| 
 | |
| 		      str = buf;
 | |
| 		      tellString(&str, &bufsize, ENC_UTF8);
 | |
| 		      rc = (*f)(argv);
 | |
| 		      toldString();
 | |
| 		      if ( !rc )
 | |
| 		        goto out;
 | |
| 		      oututf8(&state, str, bufsize);
 | |
| 		      if ( str != buf )
 | |
| 			free(str);
 | |
| 		    }
 | |
| 		  }
 | |
| 		  SHIFT;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'W':			/* write_term(Value, Options) */
 | |
| 	       { char buf[BUFSIZE];
 | |
| 		 char *str;
 | |
| 
 | |
| 		 if ( argc < 2 )
 | |
| 		 { FMT_ERROR("not enough arguments");
 | |
| 		 }
 | |
| 		 if ( state.pending_rubber )
 | |
| 		  { size_t bufsize = BUFSIZE;
 | |
| 
 | |
| 		    str = buf;
 | |
| 		    tellString(&str, &bufsize, ENC_UTF8);
 | |
| 		    rc = (int)pl_write_term(argv, argv+1);
 | |
| 		    toldString();
 | |
| 		    if ( !rc )
 | |
| 		      goto out;
 | |
| 		    oututf8(&state, str, bufsize);
 | |
| 		    if ( str != buf )
 | |
| 		      free(str);
 | |
| 		  } else
 | |
| 		  { if ( fd->position &&
 | |
| 			 fd->position->linepos == state.column )
 | |
| 		    { IOSTREAM *old = Scurout;
 | |
| 
 | |
| 		      Scurout = fd;
 | |
| 		      rc = (int)pl_write_term(argv, argv+1);
 | |
| 		      Scurout = old;
 | |
| 		      if ( !rc )
 | |
| 			goto out;
 | |
| 
 | |
| 		      state.column = fd->position->linepos;
 | |
| 		    } else
 | |
| 		    { size_t bufsize = BUFSIZE;
 | |
| 
 | |
| 		      str = buf;
 | |
| 		      tellString(&str, &bufsize, ENC_UTF8);
 | |
| 		      rc = (int)pl_write_term(argv, argv+1);
 | |
| 		      if ( !rc )
 | |
| 			goto out;
 | |
| 		      toldString();
 | |
| 		      oututf8(&state, str, bufsize);
 | |
| 		      if ( str != buf )
 | |
| 			free(str);
 | |
| 		    }
 | |
| 		  }
 | |
| 		  SHIFT;
 | |
| 		  SHIFT;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 	       }
 | |
| 	      case '@':
 | |
| 	        { char buf[BUFSIZE];
 | |
| 		  char *str = buf;
 | |
| 		  size_t bufsize = BUFSIZE;
 | |
| 		  term_t ex = 0;
 | |
| 		  int rval;
 | |
| 
 | |
| 		  if ( argc < 1 )
 | |
| 		  { FMT_ERROR("not enough arguments");
 | |
| 		  }
 | |
| 		  tellString(&str, &bufsize, ENC_UTF8);
 | |
| 		  rval = callProlog(m, argv, PL_Q_CATCH_EXCEPTION, &ex);
 | |
| 		  toldString();
 | |
| 		  oututf8(&state, str, bufsize);
 | |
| 		  if ( str != buf )
 | |
| 		    free(str);
 | |
| 
 | |
| 		  if ( !rval )
 | |
| 		  { Sunlock(fd);
 | |
| 
 | |
| 		    if ( ex )
 | |
| 		      return PL_raise_exception(ex);
 | |
| 		    else
 | |
| 		      fail;
 | |
| 		  }
 | |
| 
 | |
| 		  SHIFT;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 	        }
 | |
| 	      case '~':			/* ~ */
 | |
| 		{ rc = outchr(&state, '~');
 | |
| 		  if ( !rc )
 | |
| 		    goto out;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 'n':			/* \n */
 | |
| 	      case 'N':			/* \n if not on newline */
 | |
| 		{ if ( arg == DEFAULT )
 | |
| 		    arg = 1;
 | |
| 		  if ( c == 'N' && state.column == 0 )
 | |
| 		    arg--;
 | |
| 		  while( arg-- > 0 )
 | |
|                   { rc = outchr(&state, '\n');
 | |
| 		    if ( !rc )
 | |
| 		      goto out;
 | |
|                   }
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case 't':			/* insert tab */
 | |
| 		{ if ( state.pending_rubber >= MAXRUBBER )
 | |
| 		    FMT_ERROR("Too many tab stops");
 | |
| 
 | |
| 		  state.rub[state.pending_rubber].where = state.buffered;
 | |
| 		  state.rub[state.pending_rubber].pad   =
 | |
| 					(arg == DEFAULT ? (pl_wchar_t)' '
 | |
| 							: (pl_wchar_t)arg);
 | |
| 		  state.rub[state.pending_rubber].size = 0;
 | |
| 		  state.pending_rubber++;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      case '|':			/* set tab */
 | |
| 		{ int stop;
 | |
| 
 | |
| 		  if ( arg == DEFAULT )
 | |
| 		    arg = state.column;
 | |
| 	      case '+':			/* tab relative */
 | |
| 		  if ( arg == DEFAULT )
 | |
| 		    arg = 8;
 | |
| 		  stop = (c == '+' ? tab_stop + arg : arg);
 | |
| 
 | |
| 		  if ( state.pending_rubber == 0 ) /* nothing to distribute */
 | |
| 		  { state.rub[0].where = state.buffered;
 | |
| 		    state.rub[0].pad = ' ';
 | |
| 		    state.pending_rubber++;
 | |
| 		  }
 | |
| 		  distribute_rubber(state.rub,
 | |
| 				    state.pending_rubber,
 | |
| 				    stop - state.column);
 | |
| 		  emit_rubber(&state);
 | |
| 
 | |
| 		  state.column = tab_stop = stop;
 | |
| 		  here++;
 | |
| 		  break;
 | |
| 		}
 | |
| 	      default:
 | |
| 	      { term_t ex = PL_new_term_ref();
 | |
| 
 | |
| 		Sunlock(fd);
 | |
| 		PL_put_atom(ex, codeToAtom(c));
 | |
| 		return PL_error("format", 2, NULL, ERR_EXISTENCE,
 | |
| 				PL_new_atom("format_character"),
 | |
| 				ex);
 | |
| 	      }
 | |
| 	    }
 | |
| 	  }
 | |
| 	  break;			/* the '~' switch */
 | |
| 	}
 | |
|       default:
 | |
| 	{ rc = outchr(&state, c);
 | |
| 	  if ( !rc )
 | |
| 	    goto out;
 | |
| 	  here++;
 | |
| 	  break;
 | |
| 	}
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if ( state.pending_rubber )		/* not closed ~t: flush out */
 | |
|     emit_rubber(&state);
 | |
| 
 | |
| out:
 | |
|   Sunlock(fd);
 | |
| 
 | |
|   return rc;
 | |
| }
 | |
| 
 | |
| 
 | |
| static void
 | |
| distribute_rubber(struct rubber *r, int rn, int space)
 | |
| { if ( space > 0 )
 | |
|   { int s = space / rn;
 | |
|     int n, m;
 | |
| 
 | |
|     for(n=0; n < rn; n++)		/* give them equal size */
 | |
|       r[n].size = s;
 | |
| 					/* distribute from the center */
 | |
|     space -= s*rn;
 | |
|     for(m = rn / 2, n = 0; space; n++, space--)
 | |
|     { r[m + (n % 2 ? n : -n)].size++;
 | |
|     }
 | |
|   } else
 | |
|   { int n;
 | |
| 
 | |
|     for(n=0; n < rn; n++)		/* set all rubber to 0 */
 | |
|       r[n].size = 0;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| static int
 | |
| emit_rubber(format_state *state)
 | |
| { const char *s = baseBuffer(&state->buffer, char);
 | |
|   const char *e = &s[entriesBuffer(&state->buffer, char)];
 | |
|   struct rubber *r = state->rub;
 | |
|   int rn = state->pending_rubber;
 | |
|   size_t j;
 | |
| 
 | |
|   for(j = 0; s <= e; j++)
 | |
|   { int chr;
 | |
| 
 | |
|     if ( rn && r->where == j )
 | |
|     { size_t n;
 | |
| 
 | |
|       for(n=0; n<r->size; n++)
 | |
|       { if ( Sputcode(r->pad, state->out) < 0 )
 | |
| 	  return FALSE;
 | |
|       }
 | |
|       r++;
 | |
|       rn--;
 | |
|     }
 | |
| 
 | |
|     if ( s < e )
 | |
|     { s = utf8_get_char(s, &chr);
 | |
|       if ( Sputcode(chr, state->out) < 0 )
 | |
| 	return FALSE;
 | |
|     } else
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   discardBuffer(&state->buffer);
 | |
|   initBuffer(&state->buffer);
 | |
|   state->buffered = 0;
 | |
|   state->pending_rubber = 0;
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*  format an integer according to  a  number  of  modifiers  at various
 | |
|     radius.   `split'  is a boolean asking to put ',' between each group
 | |
|     of three digits (e.g. 67,567,288).  `div' askes to divide the number
 | |
|     by radix^`div' before printing.   `radix'  is  the  radix  used  for
 | |
|     conversion.  `n' is the number to be converted.
 | |
| 
 | |
|  ** Fri Aug 19 22:26:41 1988  jan@swivax.UUCP (Jan Wielemaker)  */
 | |
| 
 | |
| static void
 | |
| lappend(const wchar_t *l, int def, Buffer out)
 | |
| { if ( l )
 | |
|   { const wchar_t *e = l+wcslen(l);
 | |
| 
 | |
|     while (--e >= l)
 | |
|     { int c = *e;
 | |
| 
 | |
|       if ( c < 128 )
 | |
|       { addBuffer(out, c, char);
 | |
|       } else
 | |
|       { char buf[6];
 | |
| 	char *e8, *s;
 | |
| 
 | |
| 	e8=utf8_put_char(buf, c);
 | |
| 	for(s=e8; --s>=buf; )		/* must be reversed as we reverse */
 | |
| 	{ addBuffer(out, *s, char);	/* in the end */
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   } else
 | |
|   { addBuffer(out, def, char);
 | |
|   }
 | |
| }
 | |
| 
 | |
| static void
 | |
| revert_string(char *s, size_t len)
 | |
| { char *e = &s[len-1];
 | |
| 
 | |
|   for(; e>s; s++,e--)
 | |
|   { int c = *e;
 | |
| 
 | |
|     *e = *s;
 | |
|     *s = c;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static char *
 | |
| formatInteger(PL_locale *locale, int div, int radix, bool smll, Number i,
 | |
| 	     Buffer out)
 | |
| { const char *grouping = NULL;
 | |
| 
 | |
|   if ( !locale )
 | |
|   { locale = &prolog_locale;
 | |
|   } else
 | |
|   { if ( locale->grouping && locale->grouping[0] &&
 | |
| 	 locale->thousands_sep && locale->thousands_sep[0] )
 | |
|       grouping = locale->grouping;
 | |
|   }
 | |
| 
 | |
|   switch(i->type)
 | |
|   { case V_INTEGER:
 | |
|     { int64_t n = i->value.i;
 | |
| 
 | |
|       if ( n == 0 && div == 0 )
 | |
|       { addBuffer(out, '0', char);
 | |
|       } else
 | |
|       { int before = FALSE;			/* before decimal point */
 | |
| 	int negative = FALSE;
 | |
| 	int gsize = 0;
 | |
| 	int dweight;
 | |
| 
 | |
| 	negative = (n < 0);
 | |
| 
 | |
| 	while( n != 0 || div >= 0 )
 | |
| 	{ if ( div-- == 0 && !before )
 | |
| 	  { if ( !isEmptyBuffer(out) )
 | |
| 	      lappend(locale->decimal_point, '.', out);
 | |
| 	    before = TRUE;
 | |
| 	    if ( grouping )
 | |
| 	      gsize = grouping[0];
 | |
| 	  }
 | |
| 
 | |
| 	  if ( !negative )
 | |
| 	    dweight = (int)(n % radix);
 | |
| 	  else
 | |
| 	    dweight = -(int)(n % -radix);
 | |
| 
 | |
| 	  addBuffer(out, digitName(dweight, smll), char);
 | |
| 	  n /= radix;
 | |
| 
 | |
| 	  if ( --gsize == 0 && n != 0 )
 | |
| 	  { lappend(locale->thousands_sep, ',', out);
 | |
| 	    if ( grouping[1] == 0 )
 | |
| 	      gsize = grouping[0];
 | |
| 	    else if ( grouping[1] == CHAR_MAX )
 | |
| 	      gsize = 0;
 | |
| 	    else
 | |
| 	      gsize = *++grouping;
 | |
| 	  }
 | |
| 	}
 | |
| 	if ( negative )
 | |
| 	  addBuffer(out, '-', char);
 | |
|       }
 | |
| 
 | |
|       revert_string(baseBuffer(out, char), entriesBuffer(out, char));
 | |
|       addBuffer(out, EOS, char);
 | |
| 
 | |
|       return baseBuffer(out, char);
 | |
|     }
 | |
| #ifdef O_GMP
 | |
|     case V_MPZ:
 | |
|     { GET_LD
 | |
|       size_t len = mpz_sizeinbase(i->value.mpz, radix);
 | |
|       char tmp[256];
 | |
|       char *buf;
 | |
|       int rc = TRUE;
 | |
| 
 | |
|       if ( len+2 > sizeof(tmp) )
 | |
| 	buf = PL_malloc(len+2);
 | |
|       else
 | |
| 	buf = tmp;
 | |
| 
 | |
|       EXCEPTION_GUARDED({ LD->gmp.persistent++;
 | |
| 			  mpz_get_str(buf, radix, i->value.mpz);
 | |
| 			  LD->gmp.persistent--;
 | |
| 			},
 | |
| 			{ LD->gmp.persistent--;
 | |
| 			  rc = PL_rethrow();
 | |
| 			});
 | |
|       if ( !rc )
 | |
| 	return NULL;
 | |
| 
 | |
|       if ( !smll && radix > 10 )
 | |
|       { char *s;
 | |
| 
 | |
| 	for(s=buf; *s; s++)
 | |
| 	  *s = toupper(*s);
 | |
|       }
 | |
| 
 | |
|       if ( grouping || div > 0 )
 | |
|       { int before = FALSE;			/* before decimal point */
 | |
| 	int gsize = 0;
 | |
| 	char *e = buf+strlen(buf)-1;
 | |
| 
 | |
| 	while(e >= buf || div >= 0)
 | |
| 	{ if ( div-- == 0 && !before )
 | |
| 	  { if ( !isEmptyBuffer(out) )
 | |
| 	      lappend(locale->decimal_point, '.', out);
 | |
| 	    before = TRUE;
 | |
| 	    if ( grouping )
 | |
| 	      gsize = grouping[0];
 | |
| 	  }
 | |
| 
 | |
| 	  addBuffer(out, *e, char);
 | |
| 	  e--;
 | |
| 
 | |
| 	  if ( --gsize == 0 && e >= buf && *e != '-' )
 | |
| 	  { lappend(locale->thousands_sep, ',', out);
 | |
| 	    if ( grouping[1] == 0 )
 | |
| 	      gsize = grouping[0];
 | |
| 	    else if ( grouping[1] == CHAR_MAX )
 | |
| 	      gsize = 0;
 | |
| 	    else
 | |
| 	      gsize = *++grouping;
 | |
| 	  }
 | |
| 	}
 | |
| 	revert_string(baseBuffer(out, char), entriesBuffer(out, char));
 | |
|       } else
 | |
|       { addMultipleBuffer(out, buf, strlen(buf), char);
 | |
|       }
 | |
| 
 | |
|       if ( buf != tmp )
 | |
| 	PL_free(buf);
 | |
| 
 | |
|       addBuffer(out, EOS, char);
 | |
|       return baseBuffer(out, char);
 | |
|     }
 | |
| #endif /*O_GMP*/
 | |
|     default:
 | |
|       assert(0);
 | |
|       return NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| #if O_LOCALE
 | |
| 
 | |
| static int
 | |
| countGroups(const char *grouping, int len)
 | |
| { int groups = 0;
 | |
|   int gsize = grouping[0];
 | |
| 
 | |
|   while(len>0)
 | |
|   { len -= gsize;
 | |
| 
 | |
|     if ( len > 0 )
 | |
|       groups++;
 | |
| 
 | |
|     if ( grouping[1] == 0 )
 | |
|     { if ( len > 1 )
 | |
| 	groups += (len-1)/grouping[0];
 | |
|       return groups;
 | |
|     } else if ( grouping[1] == CHAR_MAX )
 | |
|     { return groups;
 | |
|     } else
 | |
|     { gsize = *++grouping;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return groups;
 | |
| }
 | |
| 
 | |
| static int
 | |
| ths_to_utf8(char *u8, const wchar_t *s, size_t len)
 | |
| { char *e = u8+len-7;
 | |
| 
 | |
|   for( ; u8<e && *s; s++)
 | |
|     u8 = utf8_put_char(u8,*s);
 | |
|   *u8 = EOS;
 | |
| 
 | |
|   return *s == 0;
 | |
| }
 | |
| 
 | |
| static int
 | |
| same_decimal_point(PL_locale *l1, PL_locale *l2)
 | |
| { if ( l1->decimal_point && l2->decimal_point &&
 | |
|        wcscmp(l1->decimal_point, l2->decimal_point) == 0 )
 | |
|     return TRUE;
 | |
|   if ( !l1->decimal_point && !l2->decimal_point )
 | |
|     return TRUE;
 | |
| 
 | |
|   return FALSE;
 | |
| }
 | |
| 
 | |
| static int
 | |
| utf8_dp(PL_locale *l, char *s, int *len)
 | |
| { if ( l->decimal_point )
 | |
|   { if ( !ths_to_utf8(s, l->decimal_point, 20) )
 | |
|       return FALSE;
 | |
|     *len = strlen(s);
 | |
|   } else
 | |
|   { *s++ = '.';
 | |
|     *s = EOS;
 | |
|     *len = 1;
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| 
 | |
| /* localizeDecimalPoint() replaces the decimal point as entered by the
 | |
|    local sensitive print functions by the one in the specified locale.
 | |
|    This is overly complicated. Needs more testing, in particular for
 | |
|    locales with (in UTF-8) multibyte decimal points.
 | |
| */
 | |
| 
 | |
| static int
 | |
| localizeDecimalPoint(PL_locale *locale, Buffer b)
 | |
| { if ( locale == GD->locale.default_locale ||
 | |
|        same_decimal_point(GD->locale.default_locale, locale) )
 | |
|     return TRUE;
 | |
| 
 | |
|   if ( locale->decimal_point && locale->decimal_point[0] )
 | |
|   { char *s = baseBuffer(b, char);
 | |
|     char *e;
 | |
|     char dp[20];  int dplen;
 | |
|     char ddp[20]; int ddplen;
 | |
| 
 | |
|     if ( !utf8_dp(locale, dp, &dplen) ||
 | |
| 	 !utf8_dp(GD->locale.default_locale, ddp, &ddplen) )
 | |
|       return FALSE;
 | |
| 
 | |
|     if ( *s == '-' )
 | |
|       s++;
 | |
|     for(e=s; *e && isDigit(*e); e++)
 | |
|       ;
 | |
| 
 | |
|     if ( strncmp(e, ddp, ddplen) == 0 )
 | |
|     { if ( dplen == ddplen )
 | |
|       { memcpy(e, dp, dplen);
 | |
|       } else
 | |
|       { char *ob = baseBuffer(b, char);
 | |
| 	if ( dplen > ddplen && !growBuffer(b, dplen-ddplen) )
 | |
| 	  return PL_no_memory();
 | |
| 	e += baseBuffer(b, char) - ob;
 | |
| 
 | |
| 	memmove(&e[dplen-ddplen], e, strlen(e)+1);
 | |
| 	memcpy(e, dp, dplen);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static int
 | |
| groupDigits(PL_locale *locale, Buffer b)
 | |
| { if ( locale->thousands_sep && locale->thousands_sep[0] &&
 | |
|        locale->grouping && locale->grouping[0] )
 | |
|   { char *s = baseBuffer(b, char);
 | |
|     char *e;
 | |
|     int groups;
 | |
| 
 | |
|     if ( *s == '-' )
 | |
|       s++;
 | |
|     for(e=s; *e && isDigit(*e); e++)
 | |
|       ;
 | |
| 
 | |
|     groups = countGroups(locale->grouping, (int)(e-s));
 | |
| 
 | |
|     if ( groups > 0 )
 | |
|     { char *o;
 | |
|       char *grouping = locale->grouping;
 | |
|       int gsize = grouping[0];
 | |
|       char ths[20];
 | |
|       int thslen;
 | |
| 
 | |
|       if ( !ths_to_utf8(ths, locale->thousands_sep, sizeof(ths)) )
 | |
| 	return FALSE;
 | |
|       thslen = strlen(ths);
 | |
| 
 | |
|       if ( !growBuffer(b, thslen*groups) )
 | |
| 	return PL_no_memory();
 | |
|       memmove(&e[groups*thslen], e, strlen(e)+1);
 | |
| 
 | |
|       e--;
 | |
|       for(o=e+groups*thslen; e>=s; )
 | |
|       { *o-- = *e--;
 | |
| 	if ( --gsize == 0 && e>=s )
 | |
| 	{ o -= thslen-1;
 | |
| 	  memcpy(o, ths, thslen);
 | |
| 	  o--;
 | |
| 	  if ( grouping[1] == 0 )
 | |
| 	    gsize = grouping[0];
 | |
| 	  else if ( grouping[1] == CHAR_MAX )
 | |
| 	    gsize = 0;
 | |
| 	  else
 | |
| 	    gsize = *++grouping;
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return TRUE;
 | |
| }
 | |
| #endif
 | |
| 
 | |
| 
 | |
| /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | |
| formatFloat(PL_locale *locale, int how, int arg, Number f, Buffer out)
 | |
| 
 | |
| formats a floating point  number  to  a   buffer.  `How'  is  the format
 | |
| specifier ([eEfgG]), `arg' the argument.
 | |
| 
 | |
| MPZ/MPQ numbers printed using the format specifier `f' are written using
 | |
| the following algorithm, courtesy of Jan Burse:
 | |
| 
 | |
|   Given: A rational n/m
 | |
|   Seeked: The ration rounded to d fractional digits.
 | |
|   Algorithm: Compute (n*10^d+m//2)//m, and place period at d.
 | |
| 
 | |
| - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
 | |
| 
 | |
| static char *
 | |
| formatFloat(PL_locale *locale, int how, int arg, Number f, Buffer out)
 | |
| { if ( arg == DEFAULT )
 | |
|     arg = 6;
 | |
| 
 | |
|   switch(f->type)
 | |
|   {
 | |
| #ifdef O_GMP
 | |
|     mpf_t mpf;
 | |
|     mpz_t t1, t2;
 | |
|     int neg;
 | |
| 
 | |
|     case V_MPZ:
 | |
|     { switch(how)
 | |
|       { case 'f':
 | |
|         { mpz_init(t1);
 | |
|           mpz_init(t2);
 | |
|           mpz_ui_pow_ui(t1, 10, arg);
 | |
|           mpz_mul(t1, f->value.mpz, t1);
 | |
|           neg = (mpz_cmp_ui(t1, 0) < 0) ? 1 : 0;
 | |
|           mpz_abs(t1, t1);
 | |
|           goto print_mpz;
 | |
|         }
 | |
|         case 'e':
 | |
| 	case 'E':
 | |
|         case 'g':
 | |
| 	case 'G':
 | |
|         { mpf_init2(mpf, arg*4);
 | |
|           mpf_set_z(mpf, f->value.mpz);
 | |
|           goto print_mpf;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|     case V_MPQ:
 | |
|     { char tmp[12];
 | |
|       int size;
 | |
|       int written = 0;
 | |
|       int fbits;
 | |
|       int digits = 0;
 | |
|       int padding;
 | |
| 
 | |
|       switch(how)
 | |
|       { case 'f':
 | |
|         { mpz_init(t1);
 | |
|           mpz_init(t2);
 | |
|           mpz_ui_pow_ui(t1, 10, arg);
 | |
|           mpz_mul(t1, mpq_numref(f->value.mpq), t1);
 | |
|           mpz_tdiv_q_ui(t2, mpq_denref(f->value.mpq), 2);
 | |
|           if (mpq_cmp_ui(f->value.mpq, 0, 1) < 0)
 | |
|           { mpz_sub(t1, t1, t2);
 | |
|             neg=1;
 | |
|           } else
 | |
|           { mpz_add(t1, t1, t2);
 | |
|             neg=0;
 | |
|           }
 | |
|           mpz_tdiv_q(t1, t1, mpq_denref(f->value.mpq));
 | |
|           mpz_abs(t1, t1);
 | |
| 
 | |
|         print_mpz:
 | |
| 
 | |
|           if (mpz_cmp_ui(t1, 0) != 0)
 | |
|           { size = mpz_sizeinbase(t1, 10) + 1; /* reserve for <null> */
 | |
|             if ( !growBuffer(out, size) )
 | |
|             { PL_no_memory();
 | |
|               return NULL;
 | |
|             }
 | |
|             digits = written = gmp_snprintf(baseBuffer(out, char), size, "%Zd", t1);
 | |
|           }
 | |
| 
 | |
|           size = digits;
 | |
|           if (neg) size++;               /* leading - */
 | |
|           if (arg) size++;               /* decimal point */
 | |
|           if (digits <= arg)             /* leading '0's */
 | |
|           { padding = (arg-digits+1);
 | |
|             size += padding;
 | |
|           } else
 | |
|           { padding = 0;
 | |
|           }
 | |
|           size++;                        /* NULL terminator */
 | |
| 
 | |
|           if ( !growBuffer(out, size) )
 | |
|           { PL_no_memory();
 | |
|             return NULL;
 | |
|           }
 | |
| 
 | |
|           if (!digits)
 | |
|           { memset(out->base, '\0', 1);
 | |
|           }
 | |
| 
 | |
|           if (neg)
 | |
|           { memmove(out->base+1, out->base, digits+1);
 | |
|             memset(out->base, '-', 1);
 | |
|             written++;
 | |
|           }
 | |
| 
 | |
|           if (padding)
 | |
|           { memmove(out->base+neg+padding, out->base+neg, written-neg+1);
 | |
|             memset(out->base+neg, '0', padding);
 | |
|             written += padding;
 | |
|           }
 | |
| 
 | |
|           if (arg)
 | |
|           { memmove(out->base+written-(arg-1), out->base+written-arg, arg+1);
 | |
|             if ( locale->decimal_point && locale->decimal_point[0] )
 | |
|               *(out->base+written-arg) = locale->decimal_point[0];
 | |
|             else
 | |
|               *(out->base+written-arg) = '.';
 | |
|             written++;
 | |
|           }
 | |
| 
 | |
|           out->top = out->base + written;
 | |
|           mpz_clear(t1);
 | |
|           mpz_clear(t2);
 | |
|           break;
 | |
|         }
 | |
|         case 'e':
 | |
| 	case 'E':
 | |
|         case 'g':
 | |
| 	case 'G':
 | |
|         { switch(how)
 | |
|           { case 'g':
 | |
|             case 'G':
 | |
|             { mpz_t iv;
 | |
|               mpz_init(iv);
 | |
|               mpz_set_q(iv, f->value.mpq);
 | |
|               fbits = (int)mpz_sizeinbase(iv, 2) + 4*arg;
 | |
|               mpz_clear(iv);
 | |
|               break;
 | |
|             }
 | |
|             default:
 | |
|               fbits = 4*arg;
 | |
|           }
 | |
|           mpf_init2(mpf, fbits);
 | |
|           mpf_set_q(mpf, f->value.mpq);
 | |
| 
 | |
|         print_mpf:
 | |
|           Ssprintf(tmp, "%%.%dF%c", arg, how);
 | |
|           size = 0;
 | |
|           written = arg+4;
 | |
|           while(written >= size)
 | |
|           { size = written+1;
 | |
| 
 | |
|             if ( !growBuffer(out, size) )	/* reserve for -.e<null> */
 | |
|             { PL_no_memory();
 | |
|               return NULL;
 | |
|             }
 | |
|             written = gmp_snprintf(baseBuffer(out, char), size, tmp, mpf);
 | |
|           }
 | |
|           mpf_clear(mpf);
 | |
|           out->top = out->base + written;
 | |
| 
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| #endif
 | |
|     case V_INTEGER:
 | |
|       promoteToFloatNumber(f);
 | |
|       /*FALLTHROUGH*/
 | |
|     case V_FLOAT:
 | |
|     { char tmp[12];
 | |
|       int written = arg+20;
 | |
|       int size = 0;
 | |
| 
 | |
|       Ssprintf(tmp, "%%.%d%c", arg, how);
 | |
|       while(written >= size)
 | |
|       { size = written+1;
 | |
| 
 | |
| 	if ( !growBuffer(out, size) )
 | |
| 	{ PL_no_memory();
 | |
| 	  return NULL;
 | |
| 	}
 | |
| 	written = snprintf(baseBuffer(out, char), size, tmp, f->value.f);
 | |
|       }
 | |
|       out->top = out->base + written;
 | |
| 
 | |
|       break;
 | |
|     }
 | |
|     default:
 | |
|       assert(0);
 | |
|       return NULL;
 | |
|   }
 | |
| 
 | |
| #if O_LOCALE
 | |
|   if ( locale )
 | |
|   { if ( !localizeDecimalPoint(locale, out) ||
 | |
| 	 !groupDigits(locale, out) )
 | |
|       return NULL;
 | |
|   }
 | |
| #endif
 | |
| 
 | |
|   return baseBuffer(out, char);
 | |
| }
 | |
| 
 | |
| //! @}
 |