diff options
author | Ludovic Courtès <ludo@gnu.org> | 2011-01-23 00:18:27 +0100 |
---|---|---|
committer | Ludovic Courtès <ludo@gnu.org> | 2011-01-23 00:37:25 +0100 |
commit | f4bc4e5934ab6f9c6ab629ac5e33174233091c2a (patch) | |
tree | 2a473da2ff558f9e0d140a8409bb88398ed023cb /libguile/print.c | |
parent | 8e43ed5d0bd035fae0ba106b245f03559cf529ec (diff) | |
download | guile-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.c | 431 |
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. |