| /* mclex.c -- lexer for Windows mc files parser. |
| Copyright (C) 2007-2024 Free Software Foundation, Inc. |
| |
| Written by Kai Tietz, Onevision. |
| |
| This file is part of GNU Binutils. |
| |
| 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; either version 3 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, write to the Free Software |
| Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA |
| 02110-1301, USA. */ |
| |
| /* This is a lexer used by the Windows rc file parser. |
| It basically just recognized a bunch of keywords. */ |
| |
| #include "sysdep.h" |
| #include "bfd.h" |
| #include "bucomm.h" |
| #include "libiberty.h" |
| #include "safe-ctype.h" |
| #include "windmc.h" |
| #include "mcparse.h" |
| |
| #include <assert.h> |
| |
| /* Exported globals. */ |
| bool mclex_want_nl = false; |
| bool mclex_want_line = false; |
| bool mclex_want_filename = false; |
| |
| /* Local globals. */ |
| static unichar *input_stream = NULL; |
| static unichar *input_stream_pos = NULL; |
| static int input_line = 1; |
| static const char *input_filename = NULL; |
| |
| void |
| mc_set_content (const unichar *src) |
| { |
| if (!src) |
| return; |
| input_stream = input_stream_pos = unichar_dup (src); |
| } |
| |
| void |
| mc_set_inputfile (const char *name) |
| { |
| if (! name || *name == 0) |
| input_filename = "-"; |
| else |
| { |
| const char *s1 = strrchr (name, '/'); |
| const char *s2 = strrchr (name, '\\'); |
| |
| if (! s1) |
| s1 = s2; |
| if (s1 && s2 && s1 < s2) |
| s1 = s2; |
| if (! s1) |
| s1 = name; |
| else |
| s1++; |
| s1 = xstrdup (s1); |
| input_filename = s1; |
| } |
| } |
| |
| static void |
| show_msg (const char *kind, const char *msg, va_list argp) |
| { |
| fprintf (stderr, "In %s at line %d: %s: ", input_filename, input_line, kind); |
| vfprintf (stderr, msg, argp); |
| fprintf (stderr, ".\n"); |
| } |
| |
| void |
| mc_warn (const char *s, ...) |
| { |
| va_list argp; |
| va_start (argp, s); |
| show_msg ("warning", s, argp); |
| va_end (argp); |
| } |
| |
| void |
| mc_fatal (const char *s, ...) |
| { |
| va_list argp; |
| va_start (argp, s); |
| show_msg ("fatal", s, argp); |
| va_end (argp); |
| xexit (1); |
| } |
| |
| |
| static void |
| mc_error (const char *s, ...) |
| { |
| va_list argp; |
| va_start (argp, s); |
| show_msg ("parser", s, argp); |
| va_end (argp); |
| } |
| |
| void |
| yyerror (const char *s) |
| { |
| mc_error (s); |
| } |
| |
| static unichar * |
| get_diff (unichar *end, unichar *start) |
| { |
| unichar *ret; |
| unichar save = *end; |
| |
| *end = 0; |
| ret = unichar_dup (start); |
| *end = save; |
| return ret; |
| } |
| |
| static rc_uint_type |
| parse_digit (unichar ch) |
| { |
| rc_uint_type base = 10, v = 0, c; |
| |
| if (ch == '0') |
| { |
| base = 8; |
| switch (input_stream_pos[0]) |
| { |
| case 'x': case 'X': base = 16; input_stream_pos++; break; |
| case 'o': case 'O': base = 8; input_stream_pos++; break; |
| case 'b': case 'B': base = 2; input_stream_pos++; break; |
| } |
| } |
| else |
| v = (rc_uint_type) (ch - '0'); |
| |
| while ((ch = input_stream_pos[0]) != 0) |
| { |
| if (ch >= 'A' && ch <= 'F') |
| c = (rc_uint_type) (ch - 'A') + 10; |
| else if (ch >= 'a' && ch <= 'f') |
| c = (rc_uint_type) (ch - 'a') + 10; |
| else if (ch >= '0' && ch <= '9') |
| c = (rc_uint_type) (ch - '0'); |
| else |
| break; |
| v *= base; |
| v += c; |
| ++input_stream_pos; |
| } |
| if (input_stream_pos[0] == 'U' || input_stream_pos[0] == 'u') |
| input_stream_pos++; |
| if (input_stream_pos[0] == 'L' || input_stream_pos[0] == 'l') |
| input_stream_pos++; |
| if (input_stream_pos[0] == 'L' || input_stream_pos[0] == 'l') |
| input_stream_pos++; |
| return v; |
| } |
| |
| static mc_keyword *keyword_top = NULL; |
| |
| const mc_keyword * |
| enum_facility (int e) |
| { |
| mc_keyword *h = keyword_top; |
| |
| while (h != NULL) |
| { |
| while (h && strcmp (h->group_name, "facility") != 0) |
| h = h->next; |
| if (e == 0) |
| return h; |
| --e; |
| if (h) |
| h = h->next; |
| } |
| return h; |
| } |
| |
| const mc_keyword * |
| enum_severity (int e) |
| { |
| mc_keyword *h = keyword_top; |
| |
| while (h != NULL) |
| { |
| while (h && strcmp (h->group_name, "severity") != 0) |
| h = h->next; |
| if (e == 0) |
| return h; |
| --e; |
| if (h) |
| h = h->next; |
| } |
| return h; |
| } |
| |
| static void |
| mc_add_keyword_ascii (const char *sz, int rid, const char *grp, rc_uint_type nv, const char *sv) |
| { |
| unichar *usz = NULL, *usv = NULL; |
| rc_uint_type usz_len; |
| |
| unicode_from_codepage (&usz_len, &usz, sz, CP_ACP); |
| if (sv) |
| unicode_from_codepage (&usz_len, &usv, sv, CP_ACP); |
| mc_add_keyword (usz, rid, grp, nv, usv); |
| } |
| |
| void |
| mc_add_keyword (unichar *usz, int rid, const char *grp, rc_uint_type nv, unichar *sv) |
| { |
| mc_keyword *p, *c, *n; |
| size_t len = unichar_len (usz); |
| |
| c = keyword_top; |
| p = NULL; |
| while (c != NULL) |
| { |
| if (c->len > len) |
| break; |
| if (c->len == len) |
| { |
| int e = memcmp (usz, c->usz, len * sizeof (unichar)); |
| |
| if (e < 0) |
| break; |
| if (! e) |
| { |
| if (! strcmp (grp, "keyword") || strcmp (c->group_name, grp) != 0) |
| fatal (_("Duplicate symbol entered into keyword list.")); |
| c->rid = rid; |
| c->nval = nv; |
| c->sval = (!sv ? NULL : unichar_dup (sv)); |
| if (! strcmp (grp, "language")) |
| { |
| const wind_language_t *lag = wind_find_language_by_id ((unsigned) nv); |
| |
| if (lag == NULL) |
| fatal ("Language ident 0x%lx is not resolvable.\n", (long) nv); |
| memcpy (&c->lang_info, lag, sizeof (*lag)); |
| } |
| return; |
| } |
| } |
| c = (p = c)->next; |
| } |
| n = xmalloc (sizeof (mc_keyword)); |
| n->next = c; |
| n->len = len; |
| n->group_name = grp; |
| n->usz = usz; |
| n->rid = rid; |
| n->nval = nv; |
| n->sval = (!sv ? NULL : unichar_dup (sv)); |
| if (! strcmp (grp, "language")) |
| { |
| const wind_language_t *lag = wind_find_language_by_id ((unsigned) nv); |
| if (lag == NULL) |
| fatal ("Language ident 0x%lx is not resolvable.\n", (long) nv); |
| memcpy (&n->lang_info, lag, sizeof (*lag)); |
| } |
| if (! p) |
| keyword_top = n; |
| else |
| p->next = n; |
| } |
| |
| static int |
| mc_token (const unichar *t, size_t len) |
| { |
| static int was_init = 0; |
| mc_keyword *k; |
| |
| if (! was_init) |
| { |
| was_init = 1; |
| mc_add_keyword_ascii ("OutputBase", MCOUTPUTBASE, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("MessageIdTypedef", MCMESSAGEIDTYPEDEF, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("SeverityNames", MCSEVERITYNAMES, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("FacilityNames", MCFACILITYNAMES, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("LanguageNames", MCLANGUAGENAMES, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("MessageId", MCMESSAGEID, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("Severity", MCSEVERITY, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("Facility", MCFACILITY, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("SymbolicName", MCSYMBOLICNAME, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("Language", MCLANGUAGE, "keyword", 0, NULL); |
| mc_add_keyword_ascii ("Success", MCTOKEN, "severity", 0, NULL); |
| mc_add_keyword_ascii ("Informational", MCTOKEN, "severity", 1, NULL); |
| mc_add_keyword_ascii ("Warning", MCTOKEN, "severity", 2, NULL); |
| mc_add_keyword_ascii ("Error", MCTOKEN, "severity", 3, NULL); |
| mc_add_keyword_ascii ("System", MCTOKEN, "facility", 0xff, NULL); |
| mc_add_keyword_ascii ("Application", MCTOKEN, "facility", 0xfff, NULL); |
| mc_add_keyword_ascii ("English", MCTOKEN, "language", 0x409, "MSG00001"); |
| } |
| k = keyword_top; |
| if (!len || !t || *t == 0) |
| return -1; |
| while (k != NULL) |
| { |
| if (k->len > len) |
| break; |
| if (k->len == len) |
| { |
| if (! memcmp (k->usz, t, len * sizeof (unichar))) |
| { |
| if (k->rid == MCTOKEN) |
| yylval.tok = k; |
| return k->rid; |
| } |
| } |
| k = k->next; |
| } |
| return -1; |
| } |
| |
| /* Skip characters in input_stream_pos up to and including a newline |
| character. Returns non-zero if the newline was found, zero otherwise. */ |
| |
| static int |
| skip_until_eol (void) |
| { |
| while (input_stream_pos[0] != 0 && input_stream_pos[0] != '\n') |
| ++input_stream_pos; |
| if (input_stream_pos[0] == 0) |
| return 0; |
| if (input_stream_pos[0] == '\n') |
| { |
| ++input_stream_pos; |
| input_line += 1; |
| } |
| return 1; |
| } |
| |
| int |
| yylex (void) |
| { |
| unichar *start_token; |
| unichar ch; |
| |
| if (! input_stream_pos) |
| { |
| fatal ("Input stream not setuped.\n"); |
| return -1; |
| } |
| |
| if (mclex_want_line) |
| { |
| start_token = input_stream_pos; |
| if (input_stream_pos[0] == 0) |
| return -1; |
| /* PR 26082: Reject a period followed by EOF. */ |
| if (input_stream_pos[0] == '.' && input_stream_pos[1] == 0) |
| return -1; |
| if (input_stream_pos[0] == '.' |
| && (input_stream_pos[1] == '\n' |
| || (input_stream_pos[1] == '\r' && input_stream_pos[2] == '\n'))) |
| { |
| mclex_want_line = false; |
| return skip_until_eol () ? MCENDLINE : -1; |
| } |
| if (!skip_until_eol ()) |
| return -1; |
| yylval.ustr = get_diff (input_stream_pos, start_token); |
| return MCLINE; |
| } |
| |
| while ((ch = input_stream_pos[0]) <= 0x20) |
| { |
| if (ch == 0) |
| return -1; |
| ++input_stream_pos; |
| if (ch == '\n') |
| input_line += 1; |
| if (mclex_want_nl && ch == '\n') |
| { |
| mclex_want_nl = false; |
| return NL; |
| } |
| } |
| start_token = input_stream_pos; |
| ++input_stream_pos; |
| if (mclex_want_filename) |
| { |
| mclex_want_filename = false; |
| if (ch == '"') |
| { |
| start_token++; |
| while ((ch = input_stream_pos[0]) != 0) |
| { |
| if (ch == '"') |
| break; |
| ++input_stream_pos; |
| } |
| yylval.ustr = get_diff (input_stream_pos, start_token); |
| if (ch == '"') |
| ++input_stream_pos; |
| } |
| else |
| { |
| while ((ch = input_stream_pos[0]) != 0) |
| { |
| if (ch <= 0x20 || ch == ')') |
| break; |
| ++input_stream_pos; |
| } |
| yylval.ustr = get_diff (input_stream_pos, start_token); |
| } |
| return MCFILENAME; |
| } |
| switch (ch) |
| { |
| case ';': |
| ++start_token; |
| if (!skip_until_eol ()) |
| return -1; |
| yylval.ustr = get_diff (input_stream_pos, start_token); |
| return MCCOMMENT; |
| case '=': |
| return '='; |
| case '(': |
| return '('; |
| case ')': |
| return ')'; |
| case '+': |
| return '+'; |
| case ':': |
| return ':'; |
| case '0': case '1': case '2': case '3': case '4': |
| case '5': case '6': case '7': case '8': case '9': |
| yylval.ival = parse_digit (ch); |
| return MCNUMBER; |
| default: |
| if (ch >= 0x40) |
| { |
| int ret; |
| while (input_stream_pos[0] >= 0x40 || (input_stream_pos[0] >= '0' && input_stream_pos[0] <= '9')) |
| ++input_stream_pos; |
| ret = mc_token (start_token, (size_t) (input_stream_pos - start_token)); |
| if (ret != -1) |
| return ret; |
| yylval.ustr = get_diff (input_stream_pos, start_token); |
| return MCIDENT; |
| } |
| mc_error ("illegal character 0x%x.", ch); |
| } |
| return -1; |
| } |