Support changing players' science box in the editor.
[freeciv.git] / utility / inputfile.c
blob4ae429a19049ab028d1ec7ce6e6fca1d6d0d4bec
1 /**********************************************************************
2 Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2, or (at your option)
6 any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
12 ***********************************************************************/
14 /**********************************************************************
15 A low-level object for reading a registry-format file.
16 original author: David Pfitzner <dwp@mso.anu.edu.au>
18 This module implements an object which is useful for reading/parsing
19 a file in the registry format of registry.c. It takes care of the
20 low-level file-reading details, and provides functions to return
21 specific "tokens" from the file. Probably this should really use
22 higher-level tools... (flex/lex bison/yacc?)
24 When the user tries to read a token, we return a (const char*)
25 pointing to some data if the token was found, or NULL otherwise.
26 The data pointed to should not be modified. The retuned pointer
27 is valid _only_ until another inputfile is performed. (So should
28 be used immediately, or fc_strdup-ed etc.)
30 The tokens recognised are as follows:
31 (Single quotes are delimiters used here, but are not part of the
32 actual tokens/strings.)
33 Most tokens can be preceeded by optional whitespace; exceptions
34 are section_name and entry_name.
36 section_name: '[foo]'
37 returned token: 'foo'
39 entry_name: 'foo =' (optional whitespace allowed before '=')
40 returned token: 'foo'
42 end_of_line: newline, or optional '#' or ';' (comment characters)
43 followed by any other chars, then newline.
44 returned token: should not be used except to check non-NULL.
46 table_start: '{'
47 returned token: should not be used except to check non-NULL.
49 table_end: '}'
50 returned token: should not be used except to check non-NULL.
52 comma: literal ','
53 returned token: should not be used except to check non-NULL.
55 value: a signed integer, or a double-quoted string, or a
56 gettext-marked double quoted string. Strings _may_ contain
57 raw embedded newlines, and escaped doublequotes, or \.
58 eg: '123', '-999', '"foo"', '_("foo")'
59 returned token: string containing number, for numeric, or string
60 starting at first doublequote for strings, but ommiting
61 trailing double-quote. Note this does _not_ translate
62 escaped doublequotes etc back to normal.
64 ***********************************************************************/
66 #ifdef HAVE_CONFIG_H
67 #include <fc_config.h>
68 #endif
70 #include <stdarg.h>
71 #include <stdio.h>
72 #include <string.h>
74 /* utility */
75 #include "astring.h"
76 #include "ioz.h"
77 #include "log.h"
78 #include "mem.h"
79 #include "shared.h" /* TRUE, FALSE */
80 #include "support.h"
82 #include "inputfile.h"
84 #define INF_DEBUG_FOUND FALSE
85 #define INF_DEBUG_NOT_FOUND FALSE
87 #define INF_MAGIC (0xabdc0132) /* arbitrary */
89 struct inputfile {
90 unsigned int magic; /* memory check */
91 char *filename; /* filename as passed to fopen */
92 fz_FILE *fp; /* read from this */
93 bool at_eof; /* flag for end-of-file */
94 struct astring cur_line; /* data from current line */
95 int cur_line_pos; /* position in current line */
96 int line_num; /* line number from file in cur_line */
97 struct astring token; /* data returned to user */
98 struct astring partial; /* used in accumulating multi-line strings;
99 used only in get_token_value, but put
100 here so it gets freed when file closed */
101 datafilename_fn_t datafn; /* function like datafilename(); use a
102 function pointer just to keep this
103 inputfile module "generic" */
104 bool in_string; /* set when reading multi-line strings,
105 to know not to handle *include at start
106 of line as include mechanism */
107 int string_start_line; /* when in_string is true, this is the
108 start line of current string */
109 struct inputfile *included_from; /* NULL for toplevel file, otherwise
110 points back to files which this one
111 has been included from */
114 /* A function to get a specific token type: */
115 typedef const char *(*get_token_fn_t)(struct inputfile *inf);
117 static const char *get_token_section_name(struct inputfile *inf);
118 static const char *get_token_entry_name(struct inputfile *inf);
119 static const char *get_token_eol(struct inputfile *inf);
120 static const char *get_token_table_start(struct inputfile *inf);
121 static const char *get_token_table_end(struct inputfile *inf);
122 static const char *get_token_comma(struct inputfile *inf);
123 static const char *get_token_value(struct inputfile *inf);
125 static struct {
126 const char *name;
127 get_token_fn_t func;
129 tok_tab[INF_TOK_LAST] =
131 { "section_name", get_token_section_name },
132 { "entry_name", get_token_entry_name },
133 { "end_of_line", get_token_eol },
134 { "table_start", get_token_table_start },
135 { "table_end", get_token_table_end },
136 { "comma", get_token_comma },
137 { "value", get_token_value },
140 static bool read_a_line(struct inputfile *inf);
142 #define inf_log(inf, level, message, ...) \
143 if (log_do_output_for_level(level)) { \
144 do_log(__FILE__, __FUNCTION__, __FC_LINE__, FALSE, level, "%s", \
145 inf_log_str(inf, message, ## __VA_ARGS__)); \
147 #define inf_warn(inf, message) \
148 inf_log(inf, LOG_NORMAL, "%s", message);
150 /**********************************************************************
151 Return true if c is a 'comment' character: '#' or ';'
152 ***********************************************************************/
153 static bool is_comment(int c)
155 return (c == '#' || c == ';');
158 /**********************************************************************
159 Set values to zeros; should have free'd/closed everything before
160 this if appropriate.
161 ***********************************************************************/
162 static void init_zeros(struct inputfile *inf)
164 fc_assert_ret(NULL != inf);
165 inf->magic = INF_MAGIC;
166 inf->filename = NULL;
167 inf->fp = NULL;
168 inf->datafn = NULL;
169 inf->included_from = NULL;
170 inf->line_num = inf->cur_line_pos = 0;
171 inf->at_eof = inf->in_string = FALSE;
172 inf->string_start_line = 0;
173 astr_init(&inf->cur_line);
174 astr_init(&inf->token);
175 astr_init(&inf->partial);
178 /**********************************************************************
179 Check sensible values for an opened inputfile.
180 ***********************************************************************/
181 static bool inf_sanity_check(struct inputfile *inf)
183 fc_assert_ret_val(NULL != inf, FALSE);
184 fc_assert_ret_val(INF_MAGIC == inf->magic, FALSE);
185 fc_assert_ret_val(NULL != inf->fp, FALSE);
186 fc_assert_ret_val(0 <= inf->line_num, FALSE);
187 fc_assert_ret_val(0 <= inf->cur_line_pos, FALSE);
188 fc_assert_ret_val(FALSE == inf->at_eof
189 || TRUE == inf->at_eof, FALSE);
190 fc_assert_ret_val(FALSE == inf->in_string
191 || TRUE == inf->in_string, FALSE);
192 #ifdef DEBUG
193 fc_assert_ret_val(0 <= inf->string_start_line, FALSE);
194 if (inf->included_from && !inf_sanity_check(inf->included_from)) {
195 return FALSE;
197 #endif /* DEBUG */
198 return TRUE;
201 /**************************************************************************
202 Return the filename the inputfile was loaded as, or "(anonymous)"
203 if this inputfile was loaded from a stream rather than from a file.
204 **************************************************************************/
205 static const char *inf_filename(struct inputfile *inf)
207 if (inf->filename) {
208 return inf->filename;
209 } else {
210 return "(anonymous)";
214 /**********************************************************************
215 Open the file, and return an allocated, initialized structure.
216 Returns NULL if the file could not be opened.
217 ***********************************************************************/
218 struct inputfile *inf_from_file(const char *filename,
219 datafilename_fn_t datafn)
221 struct inputfile *inf;
222 fz_FILE *fp;
224 fc_assert_ret_val(NULL != filename, NULL);
225 fc_assert_ret_val(0 < strlen(filename), NULL);
226 fp = fz_from_file(filename, "r", -1, 0);
227 if (!fp) {
228 return NULL;
230 log_debug("inputfile: opened \"%s\" ok", filename);
231 inf = inf_from_stream(fp, datafn);
232 inf->filename = fc_strdup(filename);
233 return inf;
236 /**********************************************************************
237 Open the stream, and return an allocated, initialized structure.
238 Returns NULL if the file could not be opened.
239 ***********************************************************************/
240 struct inputfile *inf_from_stream(fz_FILE * stream, datafilename_fn_t datafn)
242 struct inputfile *inf;
244 fc_assert_ret_val(NULL != stream, NULL);
245 inf = fc_malloc(sizeof(*inf));
246 init_zeros(inf);
248 inf->filename = NULL;
249 inf->fp = stream;
250 inf->datafn = datafn;
252 log_debug("inputfile: opened \"%s\" ok", inf_filename(inf));
253 return inf;
257 /**********************************************************************
258 Close the file and free associated memory, but don't recurse
259 included_from files, and don't free the actual memory where
260 the inf record is stored (ie, the memory where the users pointer
261 points to). This is used when closing an included file.
262 ***********************************************************************/
263 static void inf_close_partial(struct inputfile *inf)
265 fc_assert_ret(inf_sanity_check(inf));
267 log_debug("inputfile: sub-closing \"%s\"", inf_filename(inf));
269 if (fz_ferror(inf->fp) != 0) {
270 log_error("Error before closing %s: %s", inf_filename(inf),
271 fz_strerror(inf->fp));
272 fz_fclose(inf->fp);
273 inf->fp = NULL;
275 else if (fz_fclose(inf->fp) != 0) {
276 log_error("Error closing %s", inf_filename(inf));
278 if (inf->filename) {
279 free(inf->filename);
281 inf->filename = NULL;
282 astr_free(&inf->cur_line);
283 astr_free(&inf->token);
284 astr_free(&inf->partial);
286 /* assign zeros for safety if accidently re-use etc: */
287 init_zeros(inf);
288 inf->magic = ~INF_MAGIC;
290 log_debug("inputfile: sub-closed ok");
293 /**********************************************************************
294 Close the file and free associated memory, included any partially
295 recursed included files, and the memory allocated for 'inf' itself.
296 Should only be used on an actually open inputfile.
297 After this, the pointer should not be used.
298 ***********************************************************************/
299 void inf_close(struct inputfile *inf)
301 fc_assert_ret(inf_sanity_check(inf));
303 log_debug("inputfile: closing \"%s\"", inf_filename(inf));
304 if (inf->included_from) {
305 inf_close(inf->included_from);
307 inf_close_partial(inf);
308 free(inf);
309 log_debug("inputfile: closed ok");
312 /**********************************************************************
313 Return TRUE if have data for current line.
314 ***********************************************************************/
315 static bool have_line(struct inputfile *inf)
317 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
318 return !astr_empty(&inf->cur_line);
321 /**********************************************************************
322 Return TRUE if current pos is at end of current line.
323 ***********************************************************************/
324 static bool at_eol(struct inputfile *inf)
326 fc_assert_ret_val(inf_sanity_check(inf), TRUE);
327 fc_assert_ret_val(inf->cur_line_pos <= astr_len(&inf->cur_line), TRUE);
328 return (inf->cur_line_pos >= astr_len(&inf->cur_line));
331 /**********************************************************************
332 Return TRUE if current pos is at end of file.
333 ***********************************************************************/
334 bool inf_at_eof(struct inputfile *inf)
336 fc_assert_ret_val(inf_sanity_check(inf), TRUE);
337 return inf->at_eof;
340 /**********************************************************************
341 Check for an include command, which is an isolated line with:
342 *include "filename"
343 If a file is included via this mechanism, returns 1, and sets up
344 data appropriately: (*inf) will now correspond to the new file,
345 which is opened but no data read, and inf->included_from is set
346 to newly malloced memory which corresponds to the old file.
347 ***********************************************************************/
348 static bool check_include(struct inputfile *inf)
350 const char *include_prefix = "*include";
351 static size_t len = 0;
352 size_t bare_name_len;
353 char *bare_name;
354 const char *c, *bare_name_start, *full_name;
355 struct inputfile *new_inf, temp;
357 if (len==0) {
358 len = strlen(include_prefix);
360 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
361 if (inf->in_string || astr_len(&inf->cur_line) <= len
362 || inf->cur_line_pos > 0) {
363 return FALSE;
365 if (strncmp(astr_str(&inf->cur_line), include_prefix, len) != 0) {
366 return FALSE;
368 /* from here, the include-line must be well formed */
369 /* keep inf->cur_line_pos accurate just so error messages are useful */
371 /* skip any whitespace: */
372 inf->cur_line_pos = len;
373 c = astr_str(&inf->cur_line) + len;
374 while (*c != '\0' && fc_isspace(*c)){
375 c++;
378 if (*c != '\"') {
379 inf_log(inf, LOG_ERROR,
380 "Did not find opening doublequote for '*include' line");
381 return FALSE;
383 c++;
384 inf->cur_line_pos = c - astr_str(&inf->cur_line);
386 bare_name_start = c;
387 while (*c != '\0' && *c != '\"') c++;
388 if (*c != '\"') {
389 inf_log(inf, LOG_ERROR,
390 "Did not find closing doublequote for '*include' line");
391 return FALSE;
393 c++;
394 bare_name_len = c - bare_name_start;
395 bare_name = fc_malloc(bare_name_len);
396 strncpy(bare_name, bare_name_start, bare_name_len - 1);
397 bare_name[bare_name_len - 1] = '\0';
398 inf->cur_line_pos = c - astr_str(&inf->cur_line);
400 /* check rest of line is well-formed: */
401 while (*c != '\0' && fc_isspace(*c) && !is_comment(*c)) {
402 c++;
404 if (!(*c == '\0' || is_comment(*c))) {
405 inf_log(inf, LOG_ERROR, "Junk after filename for '*include' line");
406 return FALSE;
408 inf->cur_line_pos = astr_len(&inf->cur_line) - 1;
410 full_name = inf->datafn(bare_name);
411 if (!full_name) {
412 log_error("Could not find included file \"%s\"", bare_name);
413 free(bare_name);
414 return FALSE;
416 free(bare_name);
418 /* avoid recursion: (first filename may not have the same path,
419 * but will at least stop infinite recursion) */
421 struct inputfile *inc = inf;
422 do {
423 if (inc->filename && strcmp(full_name, inc->filename) == 0) {
424 log_error("Recursion trap on '*include' for \"%s\"", full_name);
425 return FALSE;
427 } while ((inc = inc->included_from));
430 new_inf = inf_from_file(full_name, inf->datafn);
432 /* Swap things around so that memory pointed to by inf (user pointer,
433 and pointer in calling functions) contains the new inputfile,
434 and newly allocated memory for new_inf contains the old inputfile.
435 This is pretty scary, lets hope it works...
437 temp = *new_inf;
438 *new_inf = *inf;
439 *inf = temp;
440 inf->included_from = new_inf;
441 return TRUE;
444 /**********************************************************************
445 Read a new line into cur_line.
446 Increments line_num and cur_line_pos.
447 Returns 0 if didn't read or other problem: treat as EOF.
448 Strips newline from input.
449 ***********************************************************************/
450 static bool read_a_line(struct inputfile *inf)
452 struct astring *line;
453 char *ret;
454 int pos;
456 fc_assert_ret_val(inf_sanity_check(inf), FALSE);
458 if (inf->at_eof) {
459 return FALSE;
462 /* abbreviation: */
463 line = &inf->cur_line;
465 /* minimum initial line length: */
466 astr_reserve(line, 80);
467 astr_clear(line);
468 pos = 0;
470 /* Read until we get a full line:
471 * At start of this loop, pos is index to trailing null
472 * (or first position) in line.
474 for(;;) {
475 ret = fz_fgets((char *) astr_str(line) + pos,
476 astr_capacity(line) - pos, inf->fp);
478 if (!ret) {
479 /* fgets failed */
480 inf->at_eof = TRUE;
481 if (inf->in_string) {
482 /* Note: Don't allow multi-line strings to cross "include"
483 * boundaries */
484 inf_log(inf, LOG_ERROR, "Multi-line string went to end-of-file");
485 return FALSE;
487 break;
490 /* Cope with \n\r line endings if not caught by library:
491 * strip off any leading \r */
492 if (0 == pos && 0 < astr_len(line) && astr_str(line)[0] == '\r') {
493 memmove((char *)astr_str(line), astr_str(line)+1, astr_len(line));
496 pos = astr_len(line);
498 if (0 < pos && astr_str(line)[pos - 1] == '\n') {
499 int end;
500 /* Cope with \r\n line endings if not caught by library:
501 * strip off any trailing \r */
502 if (1 < pos && astr_str(line)[pos - 2] == '\r') {
503 end = pos - 2;
504 } else {
505 end = pos - 1;
507 *((char *) astr_str(line) + end) = '\0';
508 break;
510 astr_reserve(line, pos * 2);
513 if (!inf->at_eof) {
514 inf->line_num++;
515 inf->cur_line_pos = 0;
517 if (check_include(inf)) {
518 return read_a_line(inf);
520 return TRUE;
521 } else {
522 astr_clear(line);
523 if (inf->included_from) {
524 /* Pop the include, and get next line from file above instead. */
525 struct inputfile *inc = inf->included_from;
526 inf_close_partial(inf);
527 *inf = *inc; /* so the user pointer in still valid
528 * (and inf pointers in calling functions) */
529 free(inc);
530 return read_a_line(inf);
532 return FALSE;
536 /**********************************************************************
537 Return a detailed log message, including information on current line
538 number etc. Message can be NULL: then just logs information on where
539 we are in the file.
540 ***********************************************************************/
541 char *inf_log_str(struct inputfile *inf, const char *message, ...)
543 va_list args;
544 static char str[512];
546 fc_assert_ret_val(inf_sanity_check(inf), NULL);
548 if (message) {
549 va_start(args, message);
550 fc_vsnprintf(str, sizeof(str), message, args);
551 va_end(args);
552 sz_strlcat(str, "\n");
553 } else {
554 str[0] = '\0';
557 cat_snprintf(str, sizeof(str), " file \"%s\", line %d, pos %d%s",
558 inf_filename(inf), inf->line_num, inf->cur_line_pos,
559 (inf->at_eof ? ", EOF" : ""));
561 if (!astr_empty(&inf->cur_line)) {
562 cat_snprintf(str, sizeof(str), "\n looking at: '%s'",
563 astr_str(&inf->cur_line) + inf->cur_line_pos);
565 if (inf->in_string) {
566 cat_snprintf(str, sizeof(str),
567 "\n processing string starting at line %d",
568 inf->string_start_line);
570 while ((inf = inf->included_from)) { /* local pointer assignment */
571 cat_snprintf(str, sizeof(str), "\n included from file \"%s\", line %d",
572 inf_filename(inf), inf->line_num);
575 return str;
578 /**********************************************************************
579 Returns token of given type from given inputfile.
580 ***********************************************************************/
581 const char *inf_token(struct inputfile *inf, enum inf_token_type type)
583 const char *c;
584 const char *name;
585 get_token_fn_t func;
587 fc_assert_ret_val(inf_sanity_check(inf), NULL);
588 fc_assert_ret_val(INF_TOK_FIRST <= type && INF_TOK_LAST > type, NULL);
590 name = tok_tab[type].name ? tok_tab[type].name : "(unnamed)";
591 func = tok_tab[type].func;
593 if (!func) {
594 log_error("token type %d (%s) not supported yet", type, name);
595 c = NULL;
596 } else {
597 while (!have_line(inf) && read_a_line(inf)) {
598 /* Nothing. */
600 if (!have_line(inf)) {
601 c = NULL;
602 } else {
603 c = func(inf);
606 if (c && INF_DEBUG_FOUND) {
607 log_debug("inputfile: found %s '%s'", name, astr_str(&inf->token));
609 return c;
612 /**********************************************************************
613 Read as many tokens of specified type as possible, discarding
614 the results; returns number of such tokens read and discarded.
615 ***********************************************************************/
616 int inf_discard_tokens(struct inputfile *inf, enum inf_token_type type)
618 int count = 0;
620 while(inf_token(inf, type))
621 count++;
622 return count;
625 /**********************************************************************
626 Returns section name in current position of inputfile. Returns NULL
627 if there is no section name on that position. Sets inputfile position
628 after section name.
629 ***********************************************************************/
630 static const char *get_token_section_name(struct inputfile *inf)
632 const char *c, *start;
634 fc_assert_ret_val(have_line(inf), NULL);
636 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
637 if (*c++ != '[') {
638 return NULL;
640 start = c;
641 while (*c != '\0' && *c != ']') {
642 c++;
644 if (*c != ']') {
645 return NULL;
647 *((char *) c) = '\0'; /* Tricky. */
648 astr_set(&inf->token, "%s", start);
649 *((char *) c) = ']'; /* Revert. */
650 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
651 return astr_str(&inf->token);
654 /**********************************************************************
655 Returns next entry name from inputfile. Skips white spaces and
656 comments. Sets inputfile position after entry name.
657 ***********************************************************************/
658 static const char *get_token_entry_name(struct inputfile *inf)
660 const char *c, *start, *end;
661 char trailing;
663 fc_assert_ret_val(have_line(inf), NULL);
665 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
666 while (*c != '\0' && fc_isspace(*c)) {
667 c++;
669 if (*c == '\0') {
670 return NULL;
672 start = c;
673 while (*c != '\0' && !fc_isspace(*c) && *c != '=' && !is_comment(*c)) {
674 c++;
676 if (!(*c != '\0' && (fc_isspace(*c) || *c == '='))) {
677 return NULL;
679 end = c;
680 while (*c != '\0' && *c != '=' && !is_comment(*c)) {
681 c++;
683 if (*c != '=') {
684 return NULL;
686 trailing = *end;
687 *((char *) end) = '\0'; /* Tricky. */
688 astr_set(&inf->token, "%s", start);
689 *((char *) end) = trailing; /* Revert. */
690 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
691 return astr_str(&inf->token);
694 /**********************************************************************
695 If inputfile is at end-of-line, frees current line, and returns " ".
696 If there is still something on that line, returns NULL.
697 ***********************************************************************/
698 static const char *get_token_eol(struct inputfile *inf)
700 const char *c;
702 fc_assert_ret_val(have_line(inf), NULL);
704 if (!at_eol(inf)) {
705 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
706 while (*c != '\0' && fc_isspace(*c)) {
707 c++;
709 if (*c != '\0' && !is_comment(*c)) {
710 return NULL;
714 /* finished with this line: say that we don't have it any more: */
715 astr_clear(&inf->cur_line);
716 inf->cur_line_pos = 0;
718 astr_set(&inf->token, " ");
719 return astr_str(&inf->token);
722 /**********************************************************************
723 Get a flag token of a single character, with optional
724 preceeding whitespace.
725 ***********************************************************************/
726 static const char *get_token_white_char(struct inputfile *inf,
727 char target)
729 const char *c;
731 fc_assert_ret_val(have_line(inf), NULL);
733 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
734 while (*c != '\0' && fc_isspace(*c)) {
735 c++;
737 if (*c != target) {
738 return NULL;
740 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
741 astr_set(&inf->token, "%c", target);
742 return astr_str(&inf->token);
745 /**********************************************************************
746 Get flag token for table start, or NULL if that is not next token.
747 ***********************************************************************/
748 static const char *get_token_table_start(struct inputfile *inf)
750 return get_token_white_char(inf, '{');
753 /**********************************************************************
754 Get flag token for table end, or NULL if that is not next token.
755 ***********************************************************************/
756 static const char *get_token_table_end(struct inputfile *inf)
758 return get_token_white_char(inf, '}');
761 /**********************************************************************
762 Get flag token comma, or NULL if that is not next token.
763 ***********************************************************************/
764 static const char *get_token_comma(struct inputfile *inf)
766 return get_token_white_char(inf, ',');
769 /**********************************************************************
770 This one is more complicated; note that it may read in multiple lines.
771 ***********************************************************************/
772 static const char *get_token_value(struct inputfile *inf)
774 struct astring *partial;
775 const char *c, *start;
776 char trailing;
777 bool has_i18n_marking = FALSE;
778 char border_character = '\"';
780 fc_assert_ret_val(have_line(inf), NULL);
782 c = astr_str(&inf->cur_line) + inf->cur_line_pos;
783 while (*c != '\0' && fc_isspace(*c)) {
784 c++;
786 if (*c == '\0') {
787 return NULL;
790 if (*c == '-' || fc_isdigit(*c)) {
791 /* a number: */
792 start = c++;
793 while(*c != '\0' && fc_isdigit(*c)) {
794 c++;
796 /* check that the trailing stuff is ok: */
797 if (!(*c == '\0' || *c == ',' || fc_isspace(*c) || is_comment(*c))) {
798 return NULL;
800 /* If its a comma, we don't want to obliterate it permanently,
801 * so rememeber it: */
802 trailing = *c;
803 *((char *) c) = '\0'; /* Tricky. */
805 inf->cur_line_pos = c - astr_str(&inf->cur_line);
806 astr_set(&inf->token, "%s", start);
808 *((char *) c) = trailing; /* Revert. */
809 return astr_str(&inf->token);
812 /* allow gettext marker: */
813 if (*c == '_' && *(c + 1) == '(') {
814 has_i18n_marking = TRUE;
815 c += 2;
816 while (*c != '\0' && fc_isspace(*c)) {
817 c++;
819 if (*c == '\0') {
820 return NULL;
824 border_character = *c;
826 if (border_character != '\"'
827 && border_character != '\''
828 && border_character != '$') {
829 /* A one-word string: maybe FALSE or TRUE. */
830 start = c;
831 while (fc_isalnum(*c)) {
832 c++;
834 /* check that the trailing stuff is ok: */
835 if (!(*c == '\0' || *c == ',' || fc_isspace(*c) || is_comment(*c))) {
836 return NULL;
838 /* If its a comma, we don't want to obliterate it permanently,
839 * so rememeber it: */
840 trailing = *c;
841 *((char *) c) = '\0'; /* Tricky. */
843 inf->cur_line_pos = c - astr_str(&inf->cur_line);
844 astr_set(&inf->token, "%s", start);
846 *((char *) c) = trailing; /* Revert. */
847 return astr_str(&inf->token);
850 /* From here, we know we have a string, we just have to find the
851 trailing (un-escaped) double-quote. We read in extra lines if
852 necessary to find it. If we _don't_ find the end-of-string
853 (that is, we come to end-of-file), we return NULL, but we
854 leave the file in at_eof, and don't try to back-up to the
855 current point. (That would be more difficult, and probably
856 not necessary: at that point we probably have a malformed
857 string/file.)
859 As we read extra lines, the string value from previous
860 lines is placed in partial.
863 /* prepare for possibly multi-line string: */
864 inf->string_start_line = inf->line_num;
865 inf->in_string = TRUE;
867 partial = &inf->partial; /* abbreviation */
868 astr_clear(partial);
870 start = c++; /* start includes the initial \", to
871 * distinguish from a number */
872 for (;;) {
873 while (*c != '\0' && *c != border_character) {
874 /* skip over escaped chars, including backslash-doublequote,
875 * and backslash-backslash: */
876 if (*c == '\\' && *(c + 1) != '\0') {
877 c++;
879 c++;
882 if (*c == border_character) {
883 /* Found end of string */
884 break;
887 astr_add(partial, "%s\n", start);
889 if (!read_a_line(inf)) {
890 /* shouldn't happen */
891 inf_log(inf, LOG_ERROR,
892 "Bad return for multi-line string from read_a_line");
893 return NULL;
895 c = start = astr_str(&inf->cur_line);
898 /* found end of string */
899 trailing = *c;
900 *((char *) c) = '\0'; /* Tricky. */
902 inf->cur_line_pos = c + 1 - astr_str(&inf->cur_line);
903 astr_set(&inf->token, "%s%s", astr_str(partial), start);
905 *((char *) c) = trailing; /* Revert. */
907 /* check gettext tag at end: */
908 if (has_i18n_marking) {
909 if (*++c == ')') {
910 inf->cur_line_pos++;
911 } else {
912 inf_warn(inf, "Missing end of i18n string marking");
915 inf->in_string = FALSE;
916 return astr_str(&inf->token);