summaryrefslogtreecommitdiff
path: root/libguile/print.c
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2011-01-23 00:18:27 +0100
committerLudovic Courtès <ludo@gnu.org>2011-01-23 00:37:25 +0100
commitf4bc4e5934ab6f9c6ab629ac5e33174233091c2a (patch)
tree2a473da2ff558f9e0d140a8409bb88398ed023cb /libguile/print.c
parent8e43ed5d0bd035fae0ba106b245f03559cf529ec (diff)
downloadguile-f4bc4e5934ab6f9c6ab629ac5e33174233091c2a.tar.gz
Rewrite `read-char', `display', etc. using iconv calls instead of libunistring.
Thanks to Bruno Haible for his suggestions. See <http://lists.gnu.org/archive/html/bug-libunistring/2010-09/msg00007.html>, for details. * libguile/ports.c (register_finalizer_for_port): Always register a finalizer for PORT. (finalize_port): Close ENTRY->input_cd and ENTRY->output_cd. (scm_new_port_table_entry): Initialize the `input_cd' and `output_cd' fields. (utf8_to_codepoint): New function. (get_codepoint): Rewrite to use `iconv' instead of libunistring. (scm_i_set_port_encoding_x): Initialize the `input_cd' and `output_cd' fields. (update_port_lf): Move upward. Use `switch' instead of `if's. * libguile/ports.h (scm_t_port)[input_cd, output_cd]: New fields. * libguile/print.c (codepoint_to_utf8, display_string): New functions. (display_character): Use `display_string'. (write_combining_character): Likewise. (iprin1): Use `display_string' instead of `scm_lfwrite_str', and `display_character' instead of `scm_putc'. (write_character): Likewise. (write_character_escaped): New function. * test-suite/tests/encoding-escapes.test ("display output escapes")["Rashomon"]: Use lower-case escapes. ["fake escape"]: New test.
Diffstat (limited to 'libguile/print.c')
-rw-r--r--libguile/print.c431
1 files changed, 265 insertions, 166 deletions
diff --git a/libguile/print.c b/libguile/print.c
index 352ca94f8..679327a8a 100644
--- a/libguile/print.c
+++ b/libguile/print.c
@@ -1,5 +1,6 @@
-/* Copyright (C) 1995-1999,2000,2001, 2002, 2003, 2004, 2006, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
- *
+/* Copyright (C) 1995-1999, 2000, 2001, 2002, 2003, 2004, 2006, 2008,
+ * 2009, 2010, 2011 Free Software Foundation, Inc.
+ *
* 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 3 of
@@ -23,6 +24,10 @@
#endif
#include <errno.h>
+#include <iconv.h>
+#include <stdio.h>
+#include <assert.h>
+
#include <uniconv.h>
#include <unictype.h>
@@ -56,10 +61,16 @@
/* Character printers. */
+static size_t display_string (const void *, int, size_t, SCM,
+ scm_t_string_failed_conversion_handler);
+
static int display_character (scm_t_wchar, SCM,
scm_t_string_failed_conversion_handler);
+
static void write_character (scm_t_wchar, SCM, int);
+static void write_character_escaped (scm_t_wchar, int, SCM);
+
/* {Names of immediate symbols}
@@ -541,16 +552,31 @@ iprin1 (SCM exp, SCM port, scm_print_state *pstate)
{
size_t len, i;
- scm_putc ('"', port);
+ display_character ('"', port, iconveh_question_mark);
len = scm_i_string_length (exp);
for (i = 0; i < len; ++i)
write_character (scm_i_string_ref (exp, i), port, 1);
- scm_putc ('"', port);
+ display_character ('"', port, iconveh_question_mark);
scm_remember_upto_here_1 (exp);
}
else
- scm_lfwrite_str (exp, port);
+ {
+ size_t len, printed;
+
+ len = scm_i_string_length (exp);
+ printed = display_string (scm_i_string_data (exp),
+ scm_i_is_narrow_string (exp),
+ len, port,
+ scm_i_get_conversion_strategy (port));
+ if (SCM_UNLIKELY (printed < len))
+ /* FIXME: Provide the error location. */
+ scm_encoding_error (__func__, errno,
+ "cannot convert to output locale",
+ "UTF-32", scm_i_get_port_encoding (port),
+ exp);
+ }
+
scm_remember_upto_here_1 (exp);
break;
case scm_tc7_symbol:
@@ -740,69 +766,162 @@ scm_prin1 (SCM exp, SCM port, int writingp)
}
}
-/* Attempt to display CH to PORT according to STRATEGY. Return non-zero
- if CH was successfully displayed, zero otherwise (e.g., if it was not
- representable in PORT's encoding.) */
-static int
-display_character (scm_t_wchar ch, SCM port,
- scm_t_string_failed_conversion_handler strategy)
+/* Convert codepoint CH to UTF-8 and store the result in UTF8. Return
+ the number of bytes of the UTF-8-encoded string. */
+static size_t
+codepoint_to_utf8 (scm_t_wchar ch, scm_t_uint8 utf8[4])
{
- int printed;
- const char *encoding;
+ size_t len;
+ scm_t_uint32 codepoint;
+
+ codepoint = (scm_t_uint32) ch;
- encoding = scm_i_get_port_encoding (port);
- if (encoding == NULL)
+ if (codepoint <= 0x7f)
{
- if (ch <= 0xff)
- {
- scm_putc (ch, port);
- printed = 1;
- }
- else
- printed = 0;
+ len = 1;
+ utf8[0] = (scm_t_uint8) codepoint;
+ }
+ else if (codepoint <= 0x7ffUL)
+ {
+ len = 2;
+ utf8[0] = 0xc0 | (codepoint >> 6);
+ utf8[1] = 0x80 | (codepoint & 0x3f);
+ }
+ else if (codepoint <= 0xffffUL)
+ {
+ len = 3;
+ utf8[0] = 0xe0 | (codepoint >> 12);
+ utf8[1] = 0x80 | ((codepoint >> 6) & 0x3f);
+ utf8[2] = 0x80 | (codepoint & 0x3f);
}
else
{
- size_t len;
- char locale_encoded[8 * sizeof (ch)], *result;
-
- len = sizeof (locale_encoded);
- result = u32_conv_to_encoding (encoding, strategy,
- (scm_t_uint32 *) &ch, 1,
- NULL, locale_encoded, &len);
- if (result != NULL)
+ len = 4;
+ utf8[0] = 0xf0 | (codepoint >> 18);
+ utf8[1] = 0x80 | ((codepoint >> 12) & 0x3f);
+ utf8[2] = 0x80 | ((codepoint >> 6) & 0x3f);
+ utf8[3] = 0x80 | (codepoint & 0x3f);
+ }
+
+ return len;
+}
+
+/* Display the LEN codepoints in STR to PORT according to STRATEGY;
+ return the number of codepoints successfully displayed. If NARROW_P,
+ then STR is interpreted as a sequence of `char', denoting a Latin-1
+ string; otherwise it's interpreted as a sequence of
+ `scm_t_wchar'. */
+static size_t
+display_string (const void *str, int narrow_p,
+ size_t len, SCM port,
+ scm_t_string_failed_conversion_handler strategy)
+
+{
+#define STR_REF(s, x) \
+ (narrow_p \
+ ? (scm_t_wchar) ((unsigned char *) (s))[x] \
+ : ((scm_t_wchar *) (s))[x])
+
+ size_t printed;
+ scm_t_port *pt;
+
+ pt = SCM_PTAB_ENTRY (port);
+
+ if (SCM_UNLIKELY (pt->output_cd == (iconv_t) -1))
+ /* Initialize the conversion descriptors. */
+ scm_i_set_port_encoding_x (port, pt->encoding);
+
+ printed = 0;
+
+ while (len > printed)
+ {
+ size_t done, utf8_len, input_left, output_left, i;
+ size_t codepoints_read, output_len;
+ char *input, *output;
+ char utf8_buf[256], encoded_output[256];
+ size_t offsets[256];
+
+ /* Convert STR to UTF-8. */
+ for (i = printed, utf8_len = 0, input = utf8_buf;
+ i < len && utf8_len + 4 < sizeof (utf8_buf);
+ i++)
{
- /* CH is graphic; print it. */
+ offsets[utf8_len] = i;
+ utf8_len += codepoint_to_utf8 (STR_REF (str, i),
+ (scm_t_uint8 *) input);
+ input = utf8_buf + utf8_len;
+ }
+
+ input = utf8_buf;
+ input_left = utf8_len;
+
+ output = encoded_output;
+ output_left = sizeof (encoded_output);
+
+ done = iconv (pt->output_cd, &input, &input_left,
+ &output, &output_left);
- if (strategy == SCM_FAILED_CONVERSION_ESCAPE_SEQUENCE)
+ output_len = sizeof (encoded_output) - output_left;
+
+ if (SCM_UNLIKELY (done == (size_t) -1))
+ {
+ /* Reset the `iconv' state. */
+ iconv (pt->output_cd, NULL, NULL, NULL, NULL);
+
+ if (errno == EILSEQ &&
+ strategy != SCM_FAILED_CONVERSION_ERROR)
{
- /* Apply the same escaping syntax as in `write_character'. */
- if (SCM_R6RS_ESCAPES_P)
+ /* Conversion failed somewhere in INPUT and we want to
+ escape or substitute the offending input character. */
+
+ /* Print the OUTPUT_LEN bytes successfully converted. */
+ scm_lfwrite (encoded_output, output_len, port);
+
+ /* See how many input codepoints these OUTPUT_LEN bytes
+ corresponds to. */
+ codepoints_read = offsets[input - utf8_buf] - printed;
+ printed += codepoints_read;
+
+ if (strategy == SCM_FAILED_CONVERSION_ESCAPE_SEQUENCE)
{
- /* LOCALE_ENCODED is large enough to store an R6RS
- `\xNNNN;' escape sequence. However, libunistring
- up to 0.9.3 (included) always returns a
- heap-allocated RESULT. */
- if (SCM_UNLIKELY (result != locale_encoded))
- result = scm_realloc (result, len * 7);
-
- scm_i_unistring_escapes_to_r6rs_escapes (result, &len);
+ scm_t_wchar ch;
+
+ /* Find CH, the offending codepoint, and escape it. */
+ ch = STR_REF (str, offsets[input - utf8_buf]);
+ write_character_escaped (ch, 1, port);
}
else
- scm_i_unistring_escapes_to_guile_escapes (result, &len);
- }
+ /* STRATEGY is `SCM_FAILED_CONVERSION_QUESTION_MARK'. */
+ display_string ("?", 1, 1, port, strategy);
- scm_lfwrite (result, len, port);
- printed = 1;
-
- if (SCM_UNLIKELY (result != locale_encoded))
- free (result);
+ printed++;
+ }
+ else
+ /* Something bad happened that we can't handle: bail out. */
+ break;
}
else
- printed = 0;
+ {
+ /* INPUT was successfully converted, entirely; print the
+ result. */
+ scm_lfwrite (encoded_output, output_len, port);
+ codepoints_read = i - printed;
+ printed += codepoints_read;
+ }
}
return printed;
+#undef STR_REF
+}
+
+/* Attempt to display CH to PORT according to STRATEGY. Return non-zero
+ if CH was successfully displayed, zero otherwise (e.g., if it was not
+ representable in PORT's encoding.) */
+static int
+display_character (scm_t_wchar ch, SCM port,
+ scm_t_string_failed_conversion_handler strategy)
+{
+ return display_string (&ch, 0, 1, port, strategy) == 1;
}
/* Attempt to pretty-print CH, a combining character, to PORT. Return
@@ -811,39 +930,101 @@ display_character (scm_t_wchar ch, SCM port,
static int
write_combining_character (scm_t_wchar ch, SCM port)
{
- int printed;
- const char *encoding;
+ scm_t_wchar str[2];
+
+ str[0] = SCM_CODEPOINT_DOTTED_CIRCLE;
+ str[1] = ch;
+
+ return display_string (str, 0, 2, port, iconveh_error) == 2;
+}
- encoding = scm_i_get_port_encoding (port);
- if (encoding != NULL)
+/* Write CH to PORT in its escaped form, using the string escape syntax
+ if STRING_ESCAPES_P is non-zero. */
+static void
+write_character_escaped (scm_t_wchar ch, int string_escapes_p, SCM port)
+{
+ if (string_escapes_p)
{
- scm_t_wchar str[2];
- char locale_encoded[sizeof (str)], *result;
- size_t len;
-
- str[0] = SCM_CODEPOINT_DOTTED_CIRCLE;
- str[1] = ch;
-
- len = sizeof (locale_encoded);
- result = u32_conv_to_encoding (encoding, iconveh_error,
- (scm_t_uint32 *) str, 2,
- NULL, locale_encoded, &len);
- if (result != NULL)
+ /* Represent CH using the in-string escape syntax. */
+
+ static const char hex[] = "0123456789abcdef";
+ static const char escapes[7] = "abtnvfr";
+ char buf[9];
+
+ if (ch >= 0x07 && ch <= 0x0D && ch != 0x0A)
{
- scm_lfwrite (result, len, port);
- printed = 1;
- if (SCM_UNLIKELY (result != locale_encoded))
- free (result);
+ /* Use special escapes for some C0 controls. */
+ buf[0] = '\\';
+ buf[1] = escapes[ch - 0x07];
+ scm_lfwrite (buf, 2, port);
+ }
+ else if (!SCM_R6RS_ESCAPES_P)
+ {
+ if (ch <= 0xFF)
+ {
+ buf[0] = '\\';
+ buf[1] = 'x';
+ buf[2] = hex[ch / 16];
+ buf[3] = hex[ch % 16];
+ scm_lfwrite (buf, 4, port);
+ }
+ else if (ch <= 0xFFFF)
+ {
+ buf[0] = '\\';
+ buf[1] = 'u';
+ buf[2] = hex[(ch & 0xF000) >> 12];
+ buf[3] = hex[(ch & 0xF00) >> 8];
+ buf[4] = hex[(ch & 0xF0) >> 4];
+ buf[5] = hex[(ch & 0xF)];
+ scm_lfwrite (buf, 6, port);
+ }
+ else if (ch > 0xFFFF)
+ {
+ buf[0] = '\\';
+ buf[1] = 'U';
+ buf[2] = hex[(ch & 0xF00000) >> 20];
+ buf[3] = hex[(ch & 0xF0000) >> 16];
+ buf[4] = hex[(ch & 0xF000) >> 12];
+ buf[5] = hex[(ch & 0xF00) >> 8];
+ buf[6] = hex[(ch & 0xF0) >> 4];
+ buf[7] = hex[(ch & 0xF)];
+ scm_lfwrite (buf, 8, port);
+ }
}
else
- /* Can't write the result to PORT. */
- printed = 0;
+ {
+ /* Print an R6RS variable-length hex escape: "\xNNNN;". */
+ scm_t_wchar ch2 = ch;
+
+ int i = 8;
+ buf[i] = ';';
+ i --;
+ if (ch == 0)
+ buf[i--] = '0';
+ else
+ while (ch2 > 0)
+ {
+ buf[i] = hex[ch2 & 0xF];
+ ch2 >>= 4;
+ i --;
+ }
+ buf[i] = 'x';
+ i --;
+ buf[i] = '\\';
+ scm_lfwrite (buf + i, 9 - i, port);
+ }
}
else
- /* PORT is Latin-1-encoded and can't display the fancy things. */
- printed = 0;
+ {
+ /* Represent CH using the character escape syntax. */
+ const char *name;
- return printed;
+ name = scm_i_charname (SCM_MAKE_CHAR (ch));
+ if (name != NULL)
+ scm_puts (name, port);
+ else
+ PRINT_CHAR_ESCAPE (ch, port);
+ }
}
/* Write CH to PORT, escaping it if it's non-graphic or not
@@ -854,25 +1035,28 @@ static void
write_character (scm_t_wchar ch, SCM port, int string_escapes_p)
{
int printed = 0;
+ scm_t_string_failed_conversion_handler strategy;
+
+ strategy = scm_i_get_conversion_strategy (port);
if (string_escapes_p)
{
/* Check if CH deserves special treatment. */
if (ch == '"' || ch == '\\')
{
- scm_putc ('\\', port);
- scm_putc (ch, port);
+ display_character ('\\', port, iconveh_question_mark);
+ display_character (ch, port, strategy);
printed = 1;
}
else if (ch == ' ' || ch == '\n')
{
- scm_putc (ch, port);
+ display_character (ch, port, strategy);
printed = 1;
}
}
else
{
- scm_puts ("#\\", port);
+ display_string ("#\\", 1, 2, port, iconveh_question_mark);
if (uc_combining_class (ch) != UC_CCC_NR)
/* Character is a combining character, so attempt to
@@ -891,93 +1075,8 @@ write_character (scm_t_wchar ch, SCM port, int string_escapes_p)
printed = display_character (ch, port, iconveh_error);
if (!printed)
- {
- /* CH isn't graphic or cannot be represented in PORT's
- encoding. */
-
- if (string_escapes_p)
- {
- /* Represent CH using the in-string escape syntax. */
-
- static const char hex[] = "0123456789abcdef";
- static const char escapes[7] = "abtnvfr";
- char buf[9];
-
- if (ch >= 0x07 && ch <= 0x0D && ch != 0x0A)
- {
- /* Use special escapes for some C0 controls. */
- buf[0] = '\\';
- buf[1] = escapes[ch - 0x07];
- scm_lfwrite (buf, 2, port);
- }
- else if (!SCM_R6RS_ESCAPES_P)
- {
- if (ch <= 0xFF)
- {
- buf[0] = '\\';
- buf[1] = 'x';
- buf[2] = hex[ch / 16];
- buf[3] = hex[ch % 16];
- scm_lfwrite (buf, 4, port);
- }
- else if (ch <= 0xFFFF)
- {
- buf[0] = '\\';
- buf[1] = 'u';
- buf[2] = hex[(ch & 0xF000) >> 12];
- buf[3] = hex[(ch & 0xF00) >> 8];
- buf[4] = hex[(ch & 0xF0) >> 4];
- buf[5] = hex[(ch & 0xF)];
- scm_lfwrite (buf, 6, port);
- }
- else if (ch > 0xFFFF)
- {
- buf[0] = '\\';
- buf[1] = 'U';
- buf[2] = hex[(ch & 0xF00000) >> 20];
- buf[3] = hex[(ch & 0xF0000) >> 16];
- buf[4] = hex[(ch & 0xF000) >> 12];
- buf[5] = hex[(ch & 0xF00) >> 8];
- buf[6] = hex[(ch & 0xF0) >> 4];
- buf[7] = hex[(ch & 0xF)];
- scm_lfwrite (buf, 8, port);
- }
- }
- else
- {
- /* Print an R6RS variable-length hex escape: "\xNNNN;". */
- scm_t_wchar ch2 = ch;
-
- int i = 8;
- buf[i] = ';';
- i --;
- if (ch == 0)
- buf[i--] = '0';
- else
- while (ch2 > 0)
- {
- buf[i] = hex[ch2 & 0xF];
- ch2 >>= 4;
- i --;
- }
- buf[i] = 'x';
- i --;
- buf[i] = '\\';
- scm_lfwrite (buf + i, 9 - i, port);
- }
- }
- else
- {
- /* Represent CH using the character escape syntax. */
- const char *name;
-
- name = scm_i_charname (SCM_MAKE_CHAR (ch));
- if (name != NULL)
- scm_puts (name, port);
- else
- PRINT_CHAR_ESCAPE (ch, port);
- }
- }
+ /* CH isn't graphic or cannot be represented in PORT's encoding. */
+ write_character_escaped (ch, string_escapes_p, port);
}
/* Print an integer.