PageRenderTime 69ms CodeModel.GetById 26ms RepoModel.GetById 1ms app.codeStats 1ms

/freeciv/utility/inputfile.c

https://github.com/acfoltzer/freeciv-web
C | 910 lines | 575 code | 104 blank | 231 comment | 166 complexity | 45b3a63927d57eb40828438695cfdf62 MD5 | raw file
Possible License(s): GPL-2.0, CC-BY-SA-3.0, BSD-3-Clause
  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.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. ***********************************************************************/
  12. /**********************************************************************
  13. A low-level object for reading a registry-format file.
  14. original author: David Pfitzner <dwp@mso.anu.edu.au>
  15. This module implements an object which is useful for reading/parsing
  16. a file in the registry format of registry.c. It takes care of the
  17. low-level file-reading details, and provides functions to return
  18. specific "tokens" from the file. Probably this should really use
  19. higher-level tools... (flex/lex bison/yacc?)
  20. When the user tries to read a token, we return a (const char*)
  21. pointing to some data if the token was found, or NULL otherwise.
  22. The data pointed to should not be modified. The retuned pointer
  23. is valid _only_ until another inputfile is performed. (So should
  24. be used immediately, or mystrdup-ed etc.)
  25. The tokens recognised are as follows:
  26. (Single quotes are delimiters used here, but are not part of the
  27. actual tokens/strings.)
  28. Most tokens can be preceeded by optional whitespace; exceptions
  29. are section_name and entry_name.
  30. section_name: '[foo]'
  31. returned token: 'foo'
  32. entry_name: 'foo =' (optional whitespace allowed before '=')
  33. returned token: 'foo'
  34. end_of_line: newline, or optional '#' or ';' (comment characters)
  35. followed by any other chars, then newline.
  36. returned token: should not be used except to check non-NULL.
  37. table_start: '{'
  38. returned token: should not be used except to check non-NULL.
  39. table_end: '}'
  40. returned token: should not be used except to check non-NULL.
  41. comma: literal ','
  42. returned token: should not be used except to check non-NULL.
  43. value: a signed integer, or a double-quoted string, or a
  44. gettext-marked double quoted string. Strings _may_ contain
  45. raw embedded newlines, and escaped doublequotes, or \.
  46. eg: '123', '-999', '"foo"', '_("foo")'
  47. returned token: string containing number, for numeric, or string
  48. starting at first doublequote for strings, but ommiting
  49. trailing double-quote. Note this does _not_ translate
  50. escaped doublequotes etc back to normal.
  51. ***********************************************************************/
  52. #ifdef HAVE_CONFIG_H
  53. #include <config.h>
  54. #endif
  55. #include <assert.h>
  56. #include <stdio.h>
  57. #include <string.h>
  58. #include "astring.h"
  59. #include "ioz.h"
  60. #include "log.h"
  61. #include "mem.h"
  62. #include "shared.h" /* TRUE, FALSE */
  63. #include "support.h"
  64. #include "inputfile.h"
  65. #define INF_DEBUG_FOUND FALSE
  66. #define INF_DEBUG_NOT_FOUND FALSE
  67. #define INF_MAGIC (0xabdc0132) /* arbitrary */
  68. struct inputfile {
  69. unsigned int magic; /* memory check */
  70. char *filename; /* filename as passed to fopen */
  71. fz_FILE *fp; /* read from this */
  72. bool at_eof; /* flag for end-of-file */
  73. struct astring cur_line; /* data from current line, or .n==0 if
  74. have not yet read in the current line */
  75. struct astring copy_line; /* original cur_line (sometimes insert nulls
  76. in cur_line for processing) */
  77. int cur_line_pos; /* position in current line */
  78. int line_num; /* line number from file in cur_line */
  79. struct astring token; /* data returned to user */
  80. struct astring partial; /* used in accumulating multi-line strings;
  81. used only in get_token_value, but put
  82. here so it gets freed when file closed */
  83. datafilename_fn_t datafn; /* function like datafilename(); use a
  84. function pointer just to keep this
  85. inputfile module "generic" */
  86. bool in_string; /* set when reading multi-line strings,
  87. to know not to handle *include at start
  88. of line as include mechanism */
  89. int string_start_line; /* when in_string is true, this is the
  90. start line of current string */
  91. struct inputfile *included_from; /* NULL for toplevel file, otherwise
  92. points back to files which this one
  93. has been included from */
  94. };
  95. /* A function to get a specific token type: */
  96. typedef const char *(*get_token_fn_t)(struct inputfile *inf);
  97. static const char *get_token_section_name(struct inputfile *inf);
  98. static const char *get_token_entry_name(struct inputfile *inf);
  99. static const char *get_token_eol(struct inputfile *inf);
  100. static const char *get_token_table_start(struct inputfile *inf);
  101. static const char *get_token_table_end(struct inputfile *inf);
  102. static const char *get_token_comma(struct inputfile *inf);
  103. static const char *get_token_value(struct inputfile *inf);
  104. static struct {
  105. const char *name;
  106. get_token_fn_t func;
  107. }
  108. tok_tab[INF_TOK_LAST] =
  109. {
  110. { "section_name", get_token_section_name },
  111. { "entry_name", get_token_entry_name },
  112. { "end_of_line", get_token_eol },
  113. { "table_start", get_token_table_start },
  114. { "table_end", get_token_table_end },
  115. { "comma", get_token_comma },
  116. { "value", get_token_value },
  117. };
  118. static bool read_a_line(struct inputfile *inf);
  119. static void inf_warn(struct inputfile *inf, const char *message);
  120. /**********************************************************************
  121. Return true if c is a 'comment' character: '#' or ';'
  122. ***********************************************************************/
  123. static bool is_comment(int c)
  124. {
  125. return (c == '#' || c == ';');
  126. }
  127. /**********************************************************************
  128. Set values to zeros; should have free'd/closed everything before
  129. this if appropriate.
  130. ***********************************************************************/
  131. static void init_zeros(struct inputfile *inf)
  132. {
  133. assert(inf != NULL);
  134. inf->magic = INF_MAGIC;
  135. inf->filename = NULL;
  136. inf->fp = NULL;
  137. inf->datafn = NULL;
  138. inf->included_from = NULL;
  139. inf->line_num = inf->cur_line_pos = 0;
  140. inf->at_eof = inf->in_string = FALSE;
  141. inf->string_start_line = 0;
  142. astr_init(&inf->cur_line);
  143. astr_init(&inf->copy_line);
  144. astr_init(&inf->token);
  145. astr_init(&inf->partial);
  146. }
  147. /**********************************************************************
  148. Check sensible values for an opened inputfile.
  149. ***********************************************************************/
  150. static void assert_sanity(struct inputfile *inf)
  151. {
  152. assert(inf != NULL);
  153. assert(inf->magic==INF_MAGIC);
  154. assert(inf->fp != NULL);
  155. assert(inf->line_num >= 0);
  156. assert(inf->cur_line_pos >= 0);
  157. assert(inf->at_eof == FALSE || inf->at_eof == TRUE);
  158. assert(inf->in_string == FALSE || inf->in_string == TRUE);
  159. #ifdef DEBUG
  160. assert(inf->string_start_line >= 0);
  161. assert(inf->cur_line.n >= 0);
  162. assert(inf->copy_line.n >= 0);
  163. assert(inf->token.n >= 0);
  164. assert(inf->partial.n >= 0);
  165. assert(inf->cur_line.n_alloc >= 0);
  166. assert(inf->copy_line.n_alloc >= 0);
  167. assert(inf->token.n_alloc >= 0);
  168. assert(inf->partial.n_alloc >= 0);
  169. if(inf->included_from) {
  170. assert_sanity(inf->included_from);
  171. }
  172. #endif
  173. }
  174. /**************************************************************************
  175. Return the filename the inputfile was loaded as, or "(anonymous)"
  176. if this inputfile was loaded from a stream rather than from a file.
  177. **************************************************************************/
  178. static const char *inf_filename(struct inputfile *inf)
  179. {
  180. if (inf->filename) {
  181. return inf->filename;
  182. } else {
  183. return "(anonymous)";
  184. }
  185. }
  186. /**********************************************************************
  187. Open the file, and return an allocated, initialized structure.
  188. Returns NULL if the file could not be opened.
  189. ***********************************************************************/
  190. struct inputfile *inf_from_file(const char *filename,
  191. datafilename_fn_t datafn)
  192. {
  193. struct inputfile *inf;
  194. fz_FILE *fp;
  195. assert(filename != NULL);
  196. assert(strlen(filename) > 0);
  197. fp = fz_from_file(filename, "r", FZ_NOT_USED, FZ_NOT_USED);
  198. if (!fp) {
  199. return NULL;
  200. }
  201. freelog(LOG_DEBUG, "inputfile: opened \"%s\" ok", filename);
  202. inf = inf_from_stream(fp, datafn);
  203. inf->filename = mystrdup(filename);
  204. return inf;
  205. }
  206. /**********************************************************************
  207. Open the stream, and return an allocated, initialized structure.
  208. Returns NULL if the file could not be opened.
  209. ***********************************************************************/
  210. struct inputfile *inf_from_stream(fz_FILE * stream, datafilename_fn_t datafn)
  211. {
  212. struct inputfile *inf;
  213. assert(stream != NULL);
  214. inf = fc_malloc(sizeof(*inf));
  215. init_zeros(inf);
  216. inf->filename = NULL;
  217. inf->fp = stream;
  218. inf->datafn = datafn;
  219. freelog(LOG_DEBUG, "inputfile: opened \"%s\" ok", inf_filename(inf));
  220. return inf;
  221. }
  222. /**********************************************************************
  223. Close the file and free associated memory, but don't recurse
  224. included_from files, and don't free the actual memory where
  225. the inf record is stored (ie, the memory where the users pointer
  226. points to). This is used when closing an included file.
  227. ***********************************************************************/
  228. static void inf_close_partial(struct inputfile *inf)
  229. {
  230. assert_sanity(inf);
  231. freelog(LOG_DEBUG, "inputfile: sub-closing \"%s\"", inf_filename(inf));
  232. if (fz_ferror(inf->fp) != 0) {
  233. freelog(LOG_ERROR, "Error before closing %s: %s", inf_filename(inf),
  234. fz_strerror(inf->fp));
  235. fz_fclose(inf->fp);
  236. inf->fp = NULL;
  237. }
  238. else if (fz_fclose(inf->fp) != 0) {
  239. freelog(LOG_ERROR, "Error closing %s", inf_filename(inf));
  240. }
  241. if (inf->filename) {
  242. free(inf->filename);
  243. }
  244. inf->filename = NULL;
  245. astr_free(&inf->cur_line);
  246. astr_free(&inf->copy_line);
  247. astr_free(&inf->token);
  248. astr_free(&inf->partial);
  249. /* assign zeros for safety if accidently re-use etc: */
  250. init_zeros(inf);
  251. inf->magic = ~INF_MAGIC;
  252. freelog(LOG_DEBUG, "inputfile: sub-closed ok");
  253. }
  254. /**********************************************************************
  255. Close the file and free associated memory, included any partially
  256. recursed included files, and the memory allocated for 'inf' itself.
  257. Should only be used on an actually open inputfile.
  258. After this, the pointer should not be used.
  259. ***********************************************************************/
  260. void inf_close(struct inputfile *inf)
  261. {
  262. assert_sanity(inf);
  263. freelog(LOG_DEBUG, "inputfile: closing \"%s\"", inf_filename(inf));
  264. if (inf->included_from) {
  265. inf_close(inf->included_from);
  266. }
  267. inf_close_partial(inf);
  268. free(inf);
  269. freelog(LOG_DEBUG, "inputfile: closed ok");
  270. }
  271. /**********************************************************************
  272. Return 1 if have data for current line.
  273. ***********************************************************************/
  274. static bool have_line(struct inputfile *inf)
  275. {
  276. assert_sanity(inf);
  277. return (inf->cur_line.n > 0);
  278. }
  279. /**********************************************************************
  280. Return 1 if current pos is at end of current line.
  281. ***********************************************************************/
  282. static bool at_eol(struct inputfile *inf)
  283. {
  284. assert_sanity(inf);
  285. assert(inf->cur_line_pos <= inf->cur_line.n);
  286. return (inf->cur_line_pos >= inf->cur_line.n - 1);
  287. }
  288. /**********************************************************************
  289. ...
  290. ***********************************************************************/
  291. bool inf_at_eof(struct inputfile *inf)
  292. {
  293. assert_sanity(inf);
  294. return inf->at_eof;
  295. }
  296. /**********************************************************************
  297. Check for an include command, which is an isolated line with:
  298. *include "filename"
  299. If a file is included via this mechanism, returns 1, and sets up
  300. data appropriately: (*inf) will now correspond to the new file,
  301. which is opened but no data read, and inf->included_from is set
  302. to newly malloced memory which corresponds to the old file.
  303. ***********************************************************************/
  304. static bool check_include(struct inputfile *inf)
  305. {
  306. const char *include_prefix = "*include";
  307. static size_t len = 0;
  308. char *bare_name, *full_name, *c;
  309. struct inputfile *new_inf, temp;
  310. if (len==0) {
  311. len = strlen(include_prefix);
  312. }
  313. assert_sanity(inf);
  314. if (inf->at_eof || inf->in_string || inf->cur_line.n <= len
  315. || inf->cur_line_pos > 0) {
  316. return FALSE;
  317. }
  318. if (strncmp(inf->cur_line.str, include_prefix, len)!=0) {
  319. return FALSE;
  320. }
  321. /* from here, the include-line must be well formed or we die */
  322. /* keep inf->cur_line_pos accurate just so error messages are useful */
  323. /* skip any whitespace: */
  324. inf->cur_line_pos = len;
  325. c = inf->cur_line.str + len;
  326. while (*c != '\0' && my_isspace(*c)) c++;
  327. if (*c != '\"') {
  328. inf_log(inf, LOG_ERROR,
  329. "Did not find opening doublequote for '*include' line");
  330. return FALSE;
  331. }
  332. c++;
  333. inf->cur_line_pos = c - inf->cur_line.str;
  334. bare_name = c;
  335. while (*c != '\0' && *c != '\"') c++;
  336. if (*c != '\"') {
  337. inf_log(inf, LOG_ERROR,
  338. "Did not find closing doublequote for '*include' line");
  339. return FALSE;
  340. }
  341. *c++ = '\0';
  342. inf->cur_line_pos = c - inf->cur_line.str;
  343. /* check rest of line is well-formed: */
  344. while (*c != '\0' && my_isspace(*c) && !is_comment(*c)) c++;
  345. if (!(*c=='\0' || is_comment(*c))) {
  346. inf_log(inf, LOG_ERROR, "Junk after filename for '*include' line");
  347. return FALSE;
  348. }
  349. inf->cur_line_pos = inf->cur_line.n-1;
  350. full_name = inf->datafn(bare_name);
  351. if (!full_name) {
  352. freelog(LOG_ERROR, "Could not find included file \"%s\"", bare_name);
  353. return FALSE;
  354. }
  355. /* avoid recursion: (first filename may not have the same path,
  356. but will at least stop infinite recursion) */
  357. {
  358. struct inputfile *inc = inf;
  359. do {
  360. if (inc->filename && strcmp(full_name, inc->filename)==0) {
  361. freelog(LOG_ERROR,
  362. "Recursion trap on '*include' for \"%s\"", full_name);
  363. return FALSE;
  364. }
  365. } while((inc=inc->included_from));
  366. }
  367. new_inf = inf_from_file(full_name, inf->datafn);
  368. /* Swap things around so that memory pointed to by inf (user pointer,
  369. and pointer in calling functions) contains the new inputfile,
  370. and newly allocated memory for new_inf contains the old inputfile.
  371. This is pretty scary, lets hope it works...
  372. */
  373. temp = *new_inf;
  374. *new_inf = *inf;
  375. *inf = temp;
  376. inf->included_from = new_inf;
  377. return TRUE;
  378. }
  379. /**********************************************************************
  380. Read a new line into cur_line; also copy to copy_line.
  381. Increments line_num and cur_line_pos.
  382. Returns 0 if didn't read or other problem: treat as EOF.
  383. Strips newline from input.
  384. ***********************************************************************/
  385. static bool read_a_line(struct inputfile *inf)
  386. {
  387. struct astring *line;
  388. char *ret;
  389. int pos;
  390. assert_sanity(inf);
  391. if (inf->at_eof)
  392. return FALSE;
  393. /* abbreviation: */
  394. line = &inf->cur_line;
  395. /* minimum initial line length: */
  396. astr_minsize(line, 80);
  397. pos = 0;
  398. /* don't print "orig line" in warnings until we have it: */
  399. inf->copy_line.n = 0;
  400. /* Read until we get a full line:
  401. * At start of this loop, pos is index to trailing null
  402. * (or first position) in line.
  403. */
  404. for(;;) {
  405. ret = fz_fgets(line->str + pos, line->n_alloc - pos, inf->fp);
  406. if (!ret) {
  407. /* fgets failed */
  408. inf->at_eof = TRUE;
  409. if (pos != 0) {
  410. inf_warn(inf, "missing newline at EOF, or failed read");
  411. /* treat as simple EOF, ignoring last line: */
  412. pos = 0;
  413. }
  414. line->str[0] = '\0';
  415. line->n = 0;
  416. if (inf->in_string) {
  417. /* Note: Don't allow multi-line strings to cross "include"
  418. boundaries */
  419. inf_log(inf, LOG_ERROR, "Multi-line string went to end-of-file");
  420. return FALSE;
  421. }
  422. if (inf->included_from) {
  423. /* Pop the include, and get next line from file above instead. */
  424. struct inputfile *inc = inf->included_from;
  425. inf_close_partial(inf);
  426. *inf = *inc; /* so the user pointer in still valid
  427. (and inf pointers in calling functions) */
  428. free(inc);
  429. return read_a_line(inf);
  430. }
  431. break;
  432. }
  433. pos += strlen(line->str + pos);
  434. line->n = pos + 1;
  435. if (line->str[pos-1] == '\n') {
  436. line->str[pos-1] = '\0';
  437. line->n--;
  438. break;
  439. }
  440. if (line->n != line->n_alloc) {
  441. freelog(LOG_VERBOSE, "inputfile: expect missing newline at EOF");
  442. }
  443. astr_minsize(line, line->n*2);
  444. }
  445. inf->line_num++;
  446. inf->cur_line_pos = 0;
  447. astr_minsize(&inf->copy_line, inf->cur_line.n + ((inf->cur_line.n == 0) ? 1 : 0));
  448. strcpy(inf->copy_line.str, inf->cur_line.str);
  449. if (check_include(inf)) {
  450. return read_a_line(inf);
  451. }
  452. return (!inf->at_eof);
  453. }
  454. /**********************************************************************
  455. Set "flag" token when we don't really want to return anything,
  456. except non-null.
  457. ***********************************************************************/
  458. static void assign_flag_token(struct astring *astr, char val)
  459. {
  460. static char flag_token[2];
  461. assert(astr != NULL);
  462. flag_token[0] = val;
  463. flag_token[1] = '\0';
  464. astr_minsize(astr, 2);
  465. strcpy(astr->str, flag_token);
  466. }
  467. /**********************************************************************
  468. Give a detailed log message, including information on
  469. current line number etc. Message can be NULL: then just logs
  470. information on where we are in the file.
  471. ***********************************************************************/
  472. void inf_log(struct inputfile *inf, int loglevel, const char *message)
  473. {
  474. assert_sanity(inf);
  475. if (message) {
  476. freelog(loglevel, "%s", message);
  477. }
  478. freelog(loglevel, " file \"%s\", line %d, pos %d%s",
  479. inf_filename(inf), inf->line_num, inf->cur_line_pos,
  480. (inf->at_eof ? ", EOF" : ""));
  481. if (inf->cur_line.str && inf->cur_line.n > 0) {
  482. freelog(loglevel, " looking at: '%s'",
  483. inf->cur_line.str+inf->cur_line_pos);
  484. }
  485. if (inf->copy_line.str && inf->copy_line.n > 0) {
  486. freelog(loglevel, " original line: '%s'", inf->copy_line.str);
  487. }
  488. if (inf->in_string) {
  489. freelog(loglevel, " processing string starting at line %d",
  490. inf->string_start_line);
  491. }
  492. while ((inf=inf->included_from)) { /* local pointer assignment */
  493. freelog(loglevel, " included from file \"%s\", line %d",
  494. inf_filename(inf), inf->line_num);
  495. }
  496. }
  497. /**********************************************************************
  498. ...
  499. ***********************************************************************/
  500. static void inf_warn(struct inputfile *inf, const char *message)
  501. {
  502. inf_log(inf, LOG_NORMAL, message);
  503. }
  504. /**********************************************************************
  505. ...
  506. ***********************************************************************/
  507. static const char *get_token(struct inputfile *inf,
  508. enum inf_token_type type,
  509. bool required)
  510. {
  511. const char *c;
  512. const char *name;
  513. get_token_fn_t func;
  514. assert_sanity(inf);
  515. assert(type>=INF_TOK_FIRST && type<INF_TOK_LAST);
  516. name = tok_tab[type].name ? tok_tab[type].name : "(unnamed)";
  517. func = tok_tab[type].func;
  518. if (!func) {
  519. freelog(LOG_ERROR, "token type %d (%s) not supported yet", type, name);
  520. c = NULL;
  521. } else {
  522. if (!have_line(inf))
  523. (void) read_a_line(inf);
  524. if (!have_line(inf)) {
  525. c = NULL;
  526. } else {
  527. c = func(inf);
  528. }
  529. }
  530. if (c) {
  531. if (INF_DEBUG_FOUND) {
  532. freelog(LOG_DEBUG, "inputfile: found %s '%s'", name, inf->token.str);
  533. }
  534. } else if (required) {
  535. freelog(LOG_FATAL, "Did not find token %s in %s line %d",
  536. name, inf->filename, inf->line_num);
  537. exit(EXIT_FAILURE);
  538. }
  539. return c;
  540. }
  541. const char *inf_token(struct inputfile *inf, enum inf_token_type type)
  542. {
  543. return get_token(inf, type, FALSE);
  544. }
  545. const char *inf_token_required(struct inputfile *inf, enum inf_token_type type)
  546. {
  547. return get_token(inf, type, TRUE);
  548. }
  549. /**********************************************************************
  550. Read as many tokens of specified type as possible, discarding
  551. the results; returns number of such tokens read and discarded.
  552. ***********************************************************************/
  553. int inf_discard_tokens(struct inputfile *inf, enum inf_token_type type)
  554. {
  555. int count = 0;
  556. while(inf_token(inf, type))
  557. count++;
  558. return count;
  559. }
  560. /**********************************************************************
  561. ...
  562. ***********************************************************************/
  563. static const char *get_token_section_name(struct inputfile *inf)
  564. {
  565. char *c, *start;
  566. assert(have_line(inf));
  567. c = inf->cur_line.str + inf->cur_line_pos;
  568. if (*c++ != '[')
  569. return NULL;
  570. start = c;
  571. while (*c != '\0' && *c != ']') {
  572. c++;
  573. }
  574. if (*c != ']')
  575. return NULL;
  576. *c++ = '\0';
  577. inf->cur_line_pos = c - inf->cur_line.str;
  578. astr_minsize(&inf->token, strlen(start)+1);
  579. strcpy(inf->token.str, start);
  580. return inf->token.str;
  581. }
  582. /**********************************************************************
  583. ...
  584. ***********************************************************************/
  585. static const char *get_token_entry_name(struct inputfile *inf)
  586. {
  587. char *c, *start, *end;
  588. assert(have_line(inf));
  589. c = inf->cur_line.str + inf->cur_line_pos;
  590. while(*c != '\0' && my_isspace(*c)) {
  591. c++;
  592. }
  593. if (*c == '\0')
  594. return NULL;
  595. start = c;
  596. while (*c != '\0' && !my_isspace(*c) && *c != '=' && !is_comment(*c)) {
  597. c++;
  598. }
  599. if (!(*c != '\0' && (my_isspace(*c) || *c == '=')))
  600. return NULL;
  601. end = c;
  602. while (*c != '\0' && *c != '=' && !is_comment(*c)) {
  603. c++;
  604. }
  605. if (*c != '=') {
  606. return NULL;
  607. }
  608. *end = '\0';
  609. inf->cur_line_pos = c + 1 - inf->cur_line.str;
  610. astr_minsize(&inf->token, strlen(start)+1);
  611. strcpy(inf->token.str, start);
  612. return inf->token.str;
  613. }
  614. /**********************************************************************
  615. ...
  616. ***********************************************************************/
  617. static const char *get_token_eol(struct inputfile *inf)
  618. {
  619. char *c;
  620. assert(have_line(inf));
  621. if (!at_eol(inf)) {
  622. c = inf->cur_line.str + inf->cur_line_pos;
  623. while(*c != '\0' && my_isspace(*c)) {
  624. c++;
  625. }
  626. if (*c != '\0' && !is_comment(*c))
  627. return NULL;
  628. }
  629. /* finished with this line: say that we don't have it any more: */
  630. inf->cur_line.n = 0;
  631. inf->cur_line_pos = 0;
  632. assign_flag_token(&inf->token, ' ');
  633. return inf->token.str;
  634. }
  635. /**********************************************************************
  636. Get a flag token of a single character, with optional
  637. preceeding whitespace.
  638. ***********************************************************************/
  639. static const char *get_token_white_char(struct inputfile *inf,
  640. char target)
  641. {
  642. char *c;
  643. assert(have_line(inf));
  644. c = inf->cur_line.str + inf->cur_line_pos;
  645. while(*c != '\0' && my_isspace(*c)) {
  646. c++;
  647. }
  648. if (*c != target)
  649. return NULL;
  650. inf->cur_line_pos = c + 1 - inf->cur_line.str;
  651. assign_flag_token(&inf->token, target);
  652. return inf->token.str;
  653. }
  654. /**********************************************************************
  655. ...
  656. ***********************************************************************/
  657. static const char *get_token_table_start(struct inputfile *inf)
  658. {
  659. return get_token_white_char(inf, '{');
  660. }
  661. /**********************************************************************
  662. ...
  663. ***********************************************************************/
  664. static const char *get_token_table_end(struct inputfile *inf)
  665. {
  666. return get_token_white_char(inf, '}');
  667. }
  668. /**********************************************************************
  669. ...
  670. ***********************************************************************/
  671. static const char *get_token_comma(struct inputfile *inf)
  672. {
  673. return get_token_white_char(inf, ',');
  674. }
  675. /**********************************************************************
  676. This one is more complicated; note that it may read in multiple lines.
  677. ***********************************************************************/
  678. static const char *get_token_value(struct inputfile *inf)
  679. {
  680. struct astring *partial;
  681. char *c, *start;
  682. char trailing;
  683. bool has_i18n_marking = FALSE;
  684. char border_character = '\"';
  685. assert(have_line(inf));
  686. c = inf->cur_line.str + inf->cur_line_pos;
  687. while(*c != '\0' && my_isspace(*c)) {
  688. c++;
  689. }
  690. if (*c == '\0')
  691. return NULL;
  692. if (*c == '-' || my_isdigit(*c)) {
  693. /* a number: */
  694. start = c++;
  695. while(*c != '\0' && my_isdigit(*c)) {
  696. c++;
  697. }
  698. /* check that the trailing stuff is ok: */
  699. if (!(*c == '\0' || *c == ',' || my_isspace(*c) || is_comment(*c))) {
  700. return NULL;
  701. }
  702. /* If its a comma, we don't want to obliterate it permanently,
  703. * so rememeber it: */
  704. trailing = *c;
  705. *c = '\0';
  706. inf->cur_line_pos = c - inf->cur_line.str;
  707. astr_minsize(&inf->token, strlen(start)+1);
  708. strcpy(inf->token.str, start);
  709. *c = trailing;
  710. return inf->token.str;
  711. }
  712. /* allow gettext marker: */
  713. if (*c == '_' && *(c+1) == '(') {
  714. has_i18n_marking = TRUE;
  715. c += 2;
  716. while(*c != '\0' && my_isspace(*c)) {
  717. c++;
  718. }
  719. if (*c == '\0')
  720. return NULL;
  721. }
  722. border_character = *c;
  723. if (border_character != '\"'
  724. && border_character != '\''
  725. && border_character != '$') {
  726. return NULL;
  727. }
  728. /* From here, we know we have a string, we just have to find the
  729. trailing (un-escaped) double-quote. We read in extra lines if
  730. necessary to find it. If we _don't_ find the end-of-string
  731. (that is, we come to end-of-file), we return NULL, but we
  732. leave the file in at_eof, and don't try to back-up to the
  733. current point. (That would be more difficult, and probably
  734. not necessary: at that point we probably have a malformed
  735. string/file.)
  736. As we read extra lines, the string value from previous
  737. lines is placed in partial.
  738. */
  739. /* prepare for possibly multi-line string: */
  740. inf->string_start_line = inf->line_num;
  741. inf->in_string = TRUE;
  742. partial = &inf->partial; /* abbreviation */
  743. astr_minsize(partial, 1);
  744. partial->str[0] = '\0';
  745. start = c++; /* start includes the initial \", to
  746. distinguish from a number */
  747. for(;;) {
  748. int pos;
  749. while(*c != '\0' && *c != border_character) {
  750. /* skip over escaped chars, including backslash-doublequote,
  751. and backslash-backslash: */
  752. if (*c == '\\' && *(c+1) != '\0') {
  753. c++;
  754. }
  755. c++;
  756. }
  757. if (*c == border_character) {
  758. /* Found end of string */
  759. break;
  760. }
  761. /* Accumulate to partial string and try more lines;
  762. * note partial->n must be _exactly_ the right size, so we
  763. * can strcpy instead of strcat-ing all the way to the end
  764. * each time. */
  765. pos = partial->n - 1;
  766. astr_minsize(partial, partial->n + c - start + 1);
  767. strcpy(partial->str + pos, start);
  768. strcpy(partial->str + partial->n - 2, "\n");
  769. if (!read_a_line(inf)) {
  770. /* shouldn't happen */
  771. inf_log(inf, LOG_ERROR,
  772. "Bad return for multi-line string from read_a_line");
  773. return NULL;
  774. }
  775. c = start = inf->cur_line.str;
  776. }
  777. /* found end of string */
  778. *c = '\0';
  779. inf->cur_line_pos = c + 1 - inf->cur_line.str;
  780. astr_minsize(&inf->token, partial->n + strlen(start));
  781. strcpy(inf->token.str, partial->str);
  782. strcpy(inf->token.str + partial->n - 1, start);
  783. /* check gettext tag at end: */
  784. if (has_i18n_marking) {
  785. if (*++c == ')') {
  786. inf->cur_line_pos++;
  787. } else {
  788. inf_warn(inf, "Missing end of i18n string marking");
  789. }
  790. }
  791. inf->in_string = FALSE;
  792. return inf->token.str;
  793. }