glibc/locale/programs/linereader.c

1010 lines
22 KiB
C

/* Copyright (C) 1996-2024 Free Software Foundation, Inc.
This file is part of the GNU C Library.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published
by the Free Software Foundation; version 2 of the License, or
(at your option) any later version.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include "localedef.h"
#include "charmap.h"
#include "error.h"
#include "linereader.h"
#include "locfile.h"
/* Prototypes for local functions. */
static struct token *get_toplvl_escape (struct linereader *lr);
static struct token *get_symname (struct linereader *lr);
static struct token *get_ident (struct linereader *lr);
static struct token *get_string (struct linereader *lr,
const struct charmap_t *charmap,
struct localedef_t *locale,
const struct repertoire_t *repertoire,
int verbose);
static bool utf8_decode (struct linereader *lr, uint8_t ch1, uint32_t *wch);
struct linereader *
lr_open (const char *fname, kw_hash_fct_t hf)
{
FILE *fp;
if (fname == NULL || strcmp (fname, "-") == 0
|| strcmp (fname, "/dev/stdin") == 0)
return lr_create (stdin, "<stdin>", hf);
else
{
fp = fopen (fname, "rm");
if (fp == NULL)
return NULL;
return lr_create (fp, fname, hf);
}
}
struct linereader *
lr_create (FILE *fp, const char *fname, kw_hash_fct_t hf)
{
struct linereader *result;
int n;
result = (struct linereader *) xmalloc (sizeof (*result));
result->fp = fp;
result->fname = xstrdup (fname);
result->buf = NULL;
result->bufsize = 0;
result->lineno = 1;
result->idx = 0;
result->comment_char = '#';
result->escape_char = '\\';
result->translate_strings = 1;
result->return_widestr = 0;
n = getdelim (&result->buf, &result->bufsize, '\n', result->fp);
if (n < 0)
{
int save = errno;
fclose (result->fp);
free ((char *) result->fname);
free (result);
errno = save;
return NULL;
}
if (n > 1 && result->buf[n - 2] == '\\' && result->buf[n - 1] == '\n')
n -= 2;
result->buf[n] = '\0';
result->bufact = n;
result->hash_fct = hf;
return result;
}
int
lr_eof (struct linereader *lr)
{
return lr->bufact = 0;
}
void
lr_ignore_rest (struct linereader *lr, int verbose)
{
if (verbose)
{
while (isspace (lr->buf[lr->idx]) && lr->buf[lr->idx] != '\n'
&& lr->buf[lr->idx] != lr->comment_char)
if (lr->buf[lr->idx] == '\0')
{
if (lr_next (lr) < 0)
return;
}
else
++lr->idx;
if (lr->buf[lr->idx] != '\n' && ! feof (lr->fp)
&& lr->buf[lr->idx] != lr->comment_char)
lr_error (lr, _("trailing garbage at end of line"));
}
/* Ignore continued line. */
while (lr->bufact > 0 && lr->buf[lr->bufact - 1] != '\n')
if (lr_next (lr) < 0)
break;
lr->idx = lr->bufact;
}
void
lr_close (struct linereader *lr)
{
fclose (lr->fp);
free (lr->buf);
free (lr);
}
int
lr_next (struct linereader *lr)
{
int n;
n = getdelim (&lr->buf, &lr->bufsize, '\n', lr->fp);
if (n < 0)
return -1;
++lr->lineno;
if (n > 1 && lr->buf[n - 2] == lr->escape_char && lr->buf[n - 1] == '\n')
{
#if 0
/* XXX Is this correct? */
/* An escaped newline character is substituted with a single <SP>. */
--n;
lr->buf[n - 1] = ' ';
#else
n -= 2;
#endif
}
lr->buf[n] = '\0';
lr->bufact = n;
lr->idx = 0;
return 0;
}
/* Defined in error.c. */
/* This variable is incremented each time `error' is called. */
extern unsigned int error_message_count;
/* The calling program should define program_name and set it to the
name of the executing program. */
extern char *program_name;
struct token *
lr_token (struct linereader *lr, const struct charmap_t *charmap,
struct localedef_t *locale, const struct repertoire_t *repertoire,
int verbose)
{
int ch;
while (1)
{
do
{
ch = lr_getc (lr);
if (ch == EOF)
{
lr->token.tok = tok_eof;
return &lr->token;
};
if (ch == '\n')
{
lr->token.tok = tok_eol;
return &lr->token;
}
}
while (isspace (ch));
if (ch != lr->comment_char)
break;
/* Is there an newline at the end of the buffer? */
if (lr->buf[lr->bufact - 1] != '\n')
{
/* No. Some people want this to mean that only the line in
the file not the logical, concatenated line is ignored.
Let's try this. */
lr->idx = lr->bufact;
continue;
}
/* Ignore rest of line. */
lr_ignore_rest (lr, 0);
lr->token.tok = tok_eol;
return &lr->token;
}
/* Match escape sequences. */
if (ch == lr->escape_char)
return get_toplvl_escape (lr);
/* Match ellipsis. */
if (ch == '.')
{
if (strncmp (&lr->buf[lr->idx], "...(2)....", 10) == 0)
{
int cnt;
for (cnt = 0; cnt < 10; ++cnt)
lr_getc (lr);
lr->token.tok = tok_ellipsis4_2;
return &lr->token;
}
if (strncmp (&lr->buf[lr->idx], "...", 3) == 0)
{
lr_getc (lr);
lr_getc (lr);
lr_getc (lr);
lr->token.tok = tok_ellipsis4;
return &lr->token;
}
if (strncmp (&lr->buf[lr->idx], "..", 2) == 0)
{
lr_getc (lr);
lr_getc (lr);
lr->token.tok = tok_ellipsis3;
return &lr->token;
}
if (strncmp (&lr->buf[lr->idx], ".(2)..", 6) == 0)
{
int cnt;
for (cnt = 0; cnt < 6; ++cnt)
lr_getc (lr);
lr->token.tok = tok_ellipsis2_2;
return &lr->token;
}
if (lr->buf[lr->idx] == '.')
{
lr_getc (lr);
lr->token.tok = tok_ellipsis2;
return &lr->token;
}
}
switch (ch)
{
case '<':
return get_symname (lr);
case '0' ... '9':
lr->token.tok = tok_number;
lr->token.val.num = ch - '0';
while (isdigit (ch = lr_getc (lr)))
{
lr->token.val.num *= 10;
lr->token.val.num += ch - '0';
}
if (isalpha (ch))
lr_error (lr, _("garbage at end of number"));
lr_ungetn (lr, 1);
return &lr->token;
case ';':
lr->token.tok = tok_semicolon;
return &lr->token;
case ',':
lr->token.tok = tok_comma;
return &lr->token;
case '(':
lr->token.tok = tok_open_brace;
return &lr->token;
case ')':
lr->token.tok = tok_close_brace;
return &lr->token;
case '"':
return get_string (lr, charmap, locale, repertoire, verbose);
case '-':
ch = lr_getc (lr);
if (ch == '1')
{
lr->token.tok = tok_minus1;
return &lr->token;
}
lr_ungetn (lr, 2);
break;
case 0x80 ... 0xff: /* UTF-8 sequence. */
{
uint32_t wch;
if (!utf8_decode (lr, ch, &wch))
{
lr->token.tok = tok_error;
return &lr->token;
}
lr->token.tok = tok_ucs4;
lr->token.val.ucs4 = wch;
return &lr->token;
}
}
return get_ident (lr);
}
static struct token *
get_toplvl_escape (struct linereader *lr)
{
/* This is supposed to be a numeric value. We return the
numerical value and the number of bytes. */
size_t start_idx = lr->idx - 1;
unsigned char *bytes = lr->token.val.charcode.bytes;
size_t nbytes = 0;
int ch;
do
{
unsigned int byte = 0;
unsigned int base = 8;
ch = lr_getc (lr);
if (ch == 'd')
{
base = 10;
ch = lr_getc (lr);
}
else if (ch == 'x')
{
base = 16;
ch = lr_getc (lr);
}
if ((base == 16 && !isxdigit (ch))
|| (base != 16 && (ch < '0' || ch >= (int) ('0' + base))))
{
esc_error:
lr->token.val.str.startmb = &lr->buf[start_idx];
while (ch != EOF && !isspace (ch))
ch = lr_getc (lr);
lr->token.val.str.lenmb = lr->idx - start_idx;
lr->token.tok = tok_error;
return &lr->token;
}
if (isdigit (ch))
byte = ch - '0';
else
byte = tolower (ch) - 'a' + 10;
ch = lr_getc (lr);
if ((base == 16 && !isxdigit (ch))
|| (base != 16 && (ch < '0' || ch >= (int) ('0' + base))))
goto esc_error;
byte *= base;
if (isdigit (ch))
byte += ch - '0';
else
byte += tolower (ch) - 'a' + 10;
ch = lr_getc (lr);
if (base != 16 && isdigit (ch))
{
byte *= base;
byte += ch - '0';
ch = lr_getc (lr);
}
bytes[nbytes++] = byte;
}
while (ch == lr->escape_char
&& nbytes < (int) sizeof (lr->token.val.charcode.bytes));
if (!isspace (ch))
lr_error (lr, _("garbage at end of character code specification"));
lr_ungetn (lr, 1);
lr->token.tok = tok_charcode;
lr->token.val.charcode.nbytes = nbytes;
return &lr->token;
}
/* Multibyte string buffer. */
struct lr_buffer
{
size_t act;
size_t max;
char *buf;
};
/* Initialize *LRB with a default-sized buffer. */
static void
lr_buffer_init (struct lr_buffer *lrb)
{
lrb->act = 0;
lrb->max = 56;
lrb->buf = xmalloc (lrb->max);
}
/* Transfers the buffer string from *LRB to LR->token.mbstr. */
static void
lr_buffer_to_token (struct lr_buffer *lrb, struct linereader *lr)
{
lr->token.val.str.startmb = xrealloc (lrb->buf, lrb->act + 1);
lr->token.val.str.startmb[lrb->act] = '\0';
lr->token.val.str.lenmb = lrb->act;
}
/* Adds CH to *LRB. */
static void
addc (struct lr_buffer *lrb, char ch)
{
if (lrb->act == lrb->max)
{
lrb->max *= 2;
lrb->buf = xrealloc (lrb->buf, lrb->max);
}
lrb->buf[lrb->act++] = ch;
}
/* Adds L bytes at S to *LRB. */
static void
adds (struct lr_buffer *lrb, const unsigned char *s, size_t l)
{
if (lrb->max - lrb->act < l)
{
size_t required_size = lrb->act + l;
size_t new_max = 2 * lrb->max;
if (new_max < required_size)
new_max = required_size;
lrb->buf = xrealloc (lrb->buf, new_max);
lrb->max = new_max;
}
memcpy (lrb->buf + lrb->act, s, l);
lrb->act += l;
}
#define ADDWC(ch) \
do \
{ \
if (buf2act == buf2max) \
{ \
buf2max *= 2; \
buf2 = xrealloc (buf2, buf2max * 4); \
} \
buf2[buf2act++] = (ch); \
} \
while (0)
static struct token *
get_symname (struct linereader *lr)
{
/* Symbol in brackets. We must distinguish three kinds:
1. reserved words
2. ISO 10646 position values
3. all other. */
const struct keyword_t *kw;
int ch;
struct lr_buffer lrb;
lr_buffer_init (&lrb);
do
{
ch = lr_getc (lr);
if (ch == lr->escape_char)
{
int c2 = lr_getc (lr);
addc (&lrb, c2);
if (c2 == '\n')
ch = '\n';
}
else
addc (&lrb, ch);
}
while (ch != '>' && ch != '\n');
if (ch == '\n')
lr_error (lr, _("unterminated symbolic name"));
/* Test for ISO 10646 position value. */
if (lrb.buf[0] == 'U' && (lrb.act == 6 || lrb.act == 10))
{
char *cp = lrb.buf + 1;
while (cp < &lrb.buf[lrb.act - 1] && isxdigit (*cp))
++cp;
if (cp == &lrb.buf[lrb.act - 1])
{
/* Yes, it is. */
lr->token.tok = tok_ucs4;
lr->token.val.ucs4 = strtoul (lrb.buf + 1, NULL, 16);
return &lr->token;
}
}
/* It is a symbolic name. Test for reserved words. */
kw = lr->hash_fct (lrb.buf, lrb.act - 1);
if (kw != NULL && kw->symname_or_ident == 1)
{
lr->token.tok = kw->token;
free (lrb.buf);
}
else
{
lr->token.tok = tok_bsymbol;
lr_buffer_to_token (&lrb, lr);
--lr->token.val.str.lenmb; /* Hide the training '>'. */
}
return &lr->token;
}
static struct token *
get_ident (struct linereader *lr)
{
const struct keyword_t *kw;
int ch;
struct lr_buffer lrb;
lr_buffer_init (&lrb);
addc (&lrb, lr->buf[lr->idx - 1]);
while (!isspace ((ch = lr_getc (lr))) && ch != '"' && ch != ';'
&& ch != '<' && ch != ',' && ch != EOF)
{
if (ch == lr->escape_char)
{
ch = lr_getc (lr);
if (ch == '\n' || ch == EOF)
{
lr_error (lr, _("invalid escape sequence"));
break;
}
}
addc (&lrb, ch);
}
lr_ungetc (lr, ch);
kw = lr->hash_fct (lrb.buf, lrb.act);
if (kw != NULL && kw->symname_or_ident == 0)
{
lr->token.tok = kw->token;
free (lrb.buf);
}
else
{
lr->token.tok = tok_ident;
lr_buffer_to_token (&lrb, lr);
}
return &lr->token;
}
/* Process a decoded Unicode codepoint WCH in a string, placing the
multibyte sequence into LRB. Return false if the character is not
found in CHARMAP/REPERTOIRE. */
static bool
translate_unicode_codepoint (struct localedef_t *locale,
const struct charmap_t *charmap,
const struct repertoire_t *repertoire,
uint32_t wch, struct lr_buffer *lrb)
{
/* See whether the charmap contains the Uxxxxxxxx names. */
char utmp[10];
snprintf (utmp, sizeof (utmp), "U%08X", wch);
struct charseq *seq = charmap_find_value (charmap, utmp, 9);
if (seq == NULL)
{
/* No, this isn't the case. Now determine from
the repertoire the name of the character and
find it in the charmap. */
if (repertoire != NULL)
{
const char *symbol = repertoire_find_symbol (repertoire, wch);
if (symbol != NULL)
seq = charmap_find_value (charmap, symbol, strlen (symbol));
}
if (seq == NULL)
{
#ifndef NO_TRANSLITERATION
/* Transliterate if possible. */
if (locale != NULL)
{
if ((locale->avail & CTYPE_LOCALE) == 0)
{
/* Load the CTYPE data now. */
int old_needed = locale->needed;
locale->needed = 0;
locale = load_locale (LC_CTYPE, locale->name,
locale->repertoire_name,
charmap, locale);
locale->needed = old_needed;
}
uint32_t *translit;
if ((locale->avail & CTYPE_LOCALE) != 0
&& ((translit = find_translit (locale, charmap, wch))
!= NULL))
/* The CTYPE data contains a matching
transliteration. */
{
for (int i = 0; translit[i] != 0; ++i)
{
snprintf (utmp, sizeof (utmp), "U%08X", translit[i]);
seq = charmap_find_value (charmap, utmp, 9);
assert (seq != NULL);
adds (lrb, seq->bytes, seq->nbytes);
}
return true;
}
}
#endif /* NO_TRANSLITERATION */
/* Not a known name. */
return false;
}
}
if (seq != NULL)
{
adds (lrb, seq->bytes, seq->nbytes);
return true;
}
else
return false;
}
/* Returns true if ch is not EOF (that is, non-negative) and a valid
UTF-8 trailing byte. */
static bool
utf8_valid_trailing (int ch)
{
return ch >= 0 && (ch & 0xc0) == 0x80;
}
/* Reports an error for a broken UTF-8 sequence. CH2 to CH4 may be
EOF. Always returns false. */
static bool
utf8_sequence_error (struct linereader *lr, uint8_t ch1, int ch2, int ch3,
int ch4)
{
char buf[38];
if (ch2 < 0)
snprintf (buf, sizeof (buf), "0x%02x", ch1);
else if (ch3 < 0)
snprintf (buf, sizeof (buf), "0x%02x 0x%02x", ch1, ch2);
else if (ch4 < 0)
snprintf (buf, sizeof (buf), "0x%02x 0x%02x 0x%02x", ch1, ch2, ch3);
else
snprintf (buf, sizeof (buf), "0x%02x 0x%02x 0x%02x 0x%02x",
ch1, ch2, ch3, ch4);
lr_error (lr, _("invalid UTF-8 sequence %s"), buf);
return false;
}
/* Reads a UTF-8 sequence from LR, with the leading byte CH1, and
stores the decoded codepoint in *WCH. Returns false on failure and
reports an error. */
static bool
utf8_decode (struct linereader *lr, uint8_t ch1, uint32_t *wch)
{
/* See RFC 3629 section 4 and __gconv_transform_utf8_internal. */
if (ch1 < 0xc2)
return utf8_sequence_error (lr, ch1, -1, -1, -1);
int ch2 = lr_getc (lr);
if (!utf8_valid_trailing (ch2))
return utf8_sequence_error (lr, ch1, ch2, -1, -1);
if (ch1 <= 0xdf)
{
uint32_t result = ((ch1 & 0x1f) << 6) | (ch2 & 0x3f);
if (result < 0x80)
return utf8_sequence_error (lr, ch1, ch2, -1, -1);
*wch = result;
return true;
}
int ch3 = lr_getc (lr);
if (!utf8_valid_trailing (ch3) || ch1 < 0xe0)
return utf8_sequence_error (lr, ch1, ch2, ch3, -1);
if (ch1 <= 0xef)
{
uint32_t result = (((ch1 & 0x0f) << 12)
| ((ch2 & 0x3f) << 6)
| (ch3 & 0x3f));
if (result < 0x800)
return utf8_sequence_error (lr, ch1, ch2, ch3, -1);
*wch = result;
return true;
}
int ch4 = lr_getc (lr);
if (!utf8_valid_trailing (ch4) || ch1 < 0xf0 || ch1 > 0xf4)
return utf8_sequence_error (lr, ch1, ch2, ch3, ch4);
uint32_t result = (((ch1 & 0x07) << 18)
| ((ch2 & 0x3f) << 12)
| ((ch3 & 0x3f) << 6)
| (ch4 & 0x3f));
if (result < 0x10000)
return utf8_sequence_error (lr, ch1, ch2, ch3, ch4);
*wch = result;
return true;
}
static struct token *
get_string (struct linereader *lr, const struct charmap_t *charmap,
struct localedef_t *locale, const struct repertoire_t *repertoire,
int verbose)
{
int return_widestr = lr->return_widestr;
struct lr_buffer lrb;
wchar_t *buf2 = NULL;
lr_buffer_init (&lrb);
/* We know it'll be a string. */
lr->token.tok = tok_string;
/* If we need not translate the strings (i.e., expand <...> parts)
we can run a simple loop. */
if (!lr->translate_strings)
{
int ch;
buf2 = NULL;
while ((ch = lr_getc (lr)) != '"' && ch != '\n' && ch != EOF)
{
if (ch >= 0x80)
lr_error (lr, _("illegal 8-bit character in untranslated string"));
addc (&lrb, ch);
}
/* Catch errors with trailing escape character. */
if (lrb.act > 0 && lrb.buf[lrb.act - 1] == lr->escape_char
&& (lrb.act == 1 || lrb.buf[lrb.act - 2] != lr->escape_char))
{
lr_error (lr, _("illegal escape sequence at end of string"));
--lrb.act;
}
else if (ch == '\n' || ch == EOF)
lr_error (lr, _("unterminated string"));
addc (&lrb, '\0');
}
else
{
bool illegal_string = false;
size_t buf2act = 0;
size_t buf2max = 56 * sizeof (uint32_t);
int ch;
/* We have to provide the wide character result as well. */
if (return_widestr)
buf2 = xmalloc (buf2max);
/* Read until the end of the string (or end of the line or file). */
while ((ch = lr_getc (lr)) != '"' && ch != '\n' && ch != EOF)
{
size_t startidx;
uint32_t wch;
struct charseq *seq;
if (ch != '<')
{
/* The standards leave it up to the implementation to
decide what to do with characters which stand for
themselves. This implementation treats the input
file as encoded in UTF-8. */
if (ch == lr->escape_char)
{
ch = lr_getc (lr);
if (ch >= 0x80)
{
lr_error (lr, _("illegal 8-bit escape sequence"));
illegal_string = true;
break;
}
if (ch == '\n' || ch == EOF)
break;
addc (&lrb, ch);
wch = ch;
}
else if (ch < 0x80)
{
wch = ch;
addc (&lrb, ch);
}
else /* UTF-8 sequence. */
{
if (!utf8_decode (lr, ch, &wch))
{
illegal_string = true;
break;
}
if (!translate_unicode_codepoint (locale, charmap,
repertoire, wch, &lrb))
{
/* Ignore the rest of the string. Callers may
skip this string because it cannot be encoded
in the output character set. */
illegal_string = true;
continue;
}
}
if (return_widestr)
ADDWC (wch);
continue;
}
/* Now we have to search for the end of the symbolic name, i.e.,
the closing '>'. */
startidx = lrb.act;
while ((ch = lr_getc (lr)) != '>' && ch != '\n' && ch != EOF)
{
if (ch == lr->escape_char)
{
ch = lr_getc (lr);
if (ch == '\n' || ch == EOF)
break;
}
addc (&lrb, ch);
}
if (ch == '\n' || ch == EOF)
/* Not a correct string. */
break;
if (lrb.act == startidx)
{
/* <> is no correct name. Ignore it and also signal an
error. */
illegal_string = true;
continue;
}
/* It might be a Uxxxx symbol. */
if (lrb.buf[startidx] == 'U'
&& (lrb.act - startidx == 5 || lrb.act - startidx == 9))
{
char *cp = lrb.buf + startidx + 1;
while (cp < &lrb.buf[lrb.act] && isxdigit (*cp))
++cp;
if (cp == &lrb.buf[lrb.act])
{
/* Yes, it is. */
addc (&lrb, '\0');
wch = strtoul (lrb.buf + startidx + 1, NULL, 16);
/* Now forget about the name we just added. */
lrb.act = startidx;
if (return_widestr)
ADDWC (wch);
if (!translate_unicode_codepoint (locale, charmap,
repertoire, wch, &lrb))
illegal_string = true;
continue;
}
}
/* We now have the symbolic name in lrb.buf[startidx] to
lrb.buf[lrb.act-1]. Now find out the value for this character
in the charmap as well as in the repertoire map (in this
order). */
seq = charmap_find_value (charmap, &lrb.buf[startidx],
lrb.act - startidx);
if (seq == NULL)
{
/* This name is not in the charmap. */
lr_error (lr, _("symbol `%.*s' not in charmap"),
(int) (lrb.act - startidx), &lrb.buf[startidx]);
illegal_string = true;
}
if (return_widestr)
{
/* Now the same for the multibyte representation. */
if (seq != NULL && seq->ucs4 != UNINITIALIZED_CHAR_VALUE)
wch = seq->ucs4;
else
{
wch = repertoire_find_value (repertoire, &lrb.buf[startidx],
lrb.act - startidx);
if (seq != NULL)
seq->ucs4 = wch;
}
if (wch == ILLEGAL_CHAR_VALUE)
{
/* This name is not in the repertoire map. */
lr_error (lr, _("symbol `%.*s' not in repertoire map"),
(int) (lrb.act - startidx), &lrb.buf[startidx]);
illegal_string = true;
}
else
ADDWC (wch);
}
/* Now forget about the name we just added. */
lrb.act = startidx;
/* And copy the bytes. */
if (seq != NULL)
adds (&lrb, seq->bytes, seq->nbytes);
}
if (ch == '\n' || ch == EOF)
{
lr_error (lr, _("unterminated string"));
illegal_string = true;
}
if (illegal_string)
{
free (lrb.buf);
free (buf2);
lr->token.val.str.startmb = NULL;
lr->token.val.str.lenmb = 0;
lr->token.val.str.startwc = NULL;
lr->token.val.str.lenwc = 0;
return &lr->token;
}
addc (&lrb, '\0');
if (return_widestr)
{
ADDWC (0);
lr->token.val.str.startwc = xrealloc (buf2,
buf2act * sizeof (uint32_t));
lr->token.val.str.lenwc = buf2act;
}
}
lr_buffer_to_token (&lrb, lr);
return &lr->token;
}