a2223c7302
doesn't depend on the 'comp' set. ok espie@ deraadt@
1922 lines
47 KiB
C
1922 lines
47 KiB
C
/*
|
|
* (c) Thomas Pornin 1999 - 2002
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 4. The name of the authors may not be used to endorse or promote
|
|
* products derived from this software without specific prior written
|
|
* permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
|
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
|
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
|
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include "tune.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stddef.h>
|
|
#include <limits.h>
|
|
#include "ucppi.h"
|
|
#include "mem.h"
|
|
#include "nhash.h"
|
|
|
|
/*
|
|
* we store macros in a hash table, and retrieve them using their name
|
|
* as identifier.
|
|
*/
|
|
static HTT macros;
|
|
static int macros_init_done = 0;
|
|
|
|
static void del_macro(void *m)
|
|
{
|
|
struct macro *n = m;
|
|
size_t i;
|
|
|
|
for (i = 0; (int)i < n->narg; i ++) freemem(n->arg[i]);
|
|
if (n->narg > 0) freemem(n->arg);
|
|
#ifdef LOW_MEM
|
|
if (n->cval.length) freemem(n->cval.t);
|
|
#else
|
|
if (n->val.nt) {
|
|
for (i = 0; i < n->val.nt; i ++)
|
|
if (S_TOKEN(n->val.t[i].type))
|
|
freemem(n->val.t[i].name);
|
|
freemem(n->val.t);
|
|
}
|
|
#endif
|
|
freemem(n);
|
|
}
|
|
|
|
static inline struct macro *new_macro(void)
|
|
{
|
|
struct macro *m = getmem(sizeof(struct macro));
|
|
|
|
m->narg = -1;
|
|
m->nest = 0;
|
|
#ifdef LOW_MEM
|
|
m->cval.length = 0;
|
|
#else
|
|
m->val.nt = m->val.art = 0;
|
|
#endif
|
|
m->vaarg = 0;
|
|
return m;
|
|
}
|
|
|
|
/*
|
|
* for special macros, and the "defined" operator
|
|
*/
|
|
enum {
|
|
MAC_NONE, MAC_DEFINED,
|
|
MAC_LINE, MAC_FILE, MAC_DATE, MAC_TIME, MAC_STDC, MAC_PRAGMA
|
|
};
|
|
#define MAC_SPECIAL MAC_LINE
|
|
|
|
/*
|
|
* returns 1 for "defined"
|
|
* returns x > 1 for a special macro such as __FILE__
|
|
* returns 0 otherwise
|
|
*/
|
|
static inline int check_special_macro(char *name)
|
|
{
|
|
if (!strcmp(name, "defined")) return MAC_DEFINED;
|
|
if (*name != '_') return MAC_NONE;
|
|
if (*(name + 1) == 'P') {
|
|
if (!strcmp(name, "_Pragma")) return MAC_PRAGMA;
|
|
return MAC_NONE;
|
|
} else if (*(name + 1) != '_') return MAC_NONE;
|
|
if (no_special_macros) return MAC_NONE;
|
|
if (!strcmp(name, "__LINE__")) return MAC_LINE;
|
|
else if (!strcmp(name, "__FILE__")) return MAC_FILE;
|
|
else if (!strcmp(name, "__DATE__")) return MAC_DATE;
|
|
else if (!strcmp(name, "__TIME__")) return MAC_TIME;
|
|
else if (!strcmp(name, "__STDC__")) return MAC_STDC;
|
|
return MAC_NONE;
|
|
}
|
|
|
|
int c99_compliant = 1;
|
|
int c99_hosted = 1;
|
|
|
|
/*
|
|
* add the special macros to the macro table
|
|
*/
|
|
static void add_special_macros(void)
|
|
{
|
|
struct macro *m;
|
|
|
|
HTT_put(¯os, new_macro(), "__LINE__");
|
|
HTT_put(¯os, new_macro(), "__FILE__");
|
|
HTT_put(¯os, new_macro(), "__DATE__");
|
|
HTT_put(¯os, new_macro(), "__TIME__");
|
|
HTT_put(¯os, new_macro(), "__STDC__");
|
|
m = new_macro(); m->narg = 1;
|
|
m->arg = getmem(sizeof(char *)); m->arg[0] = sdup("foo");
|
|
HTT_put(¯os, m, "_Pragma");
|
|
if (c99_compliant) {
|
|
#ifndef LOW_MEM
|
|
struct token t;
|
|
#endif
|
|
|
|
m = new_macro();
|
|
#ifdef LOW_MEM
|
|
m->cval.t = getmem(9);
|
|
m->cval.t[0] = NUMBER;
|
|
mmv(m->cval.t + 1, "199901L", 8);
|
|
m->cval.length = 9;
|
|
#else
|
|
t.type = NUMBER;
|
|
t.line = 0;
|
|
t.name = sdup("199901L");
|
|
aol(m->val.t, m->val.nt, t, TOKEN_LIST_MEMG);
|
|
#endif
|
|
HTT_put(¯os, m, "__STDC_VERSION__");
|
|
}
|
|
if (c99_hosted) {
|
|
#ifndef LOW_MEM
|
|
struct token t;
|
|
#endif
|
|
|
|
m = new_macro();
|
|
#ifdef LOW_MEM
|
|
m->cval.t = getmem(3);
|
|
m->cval.t[0] = NUMBER;
|
|
mmv(m->cval.t + 1, "1", 2);
|
|
m->cval.length = 3;
|
|
#else
|
|
t.type = NUMBER;
|
|
t.line = 0;
|
|
t.name = sdup("1");
|
|
aol(m->val.t, m->val.nt, t, TOKEN_LIST_MEMG);
|
|
#endif
|
|
HTT_put(¯os, m, "__STDC_HOSTED__");
|
|
}
|
|
}
|
|
|
|
#ifdef LOW_MEM
|
|
/*
|
|
* We store macro arguments as a single-byte token MACROARG, followed
|
|
* by the argument number as a one or two-byte value. If the argument
|
|
* number is between 0 and 127 (inclusive), it is stored as such in
|
|
* a single byte. Otherwise, it is supposed to be a 14-bit number, with
|
|
* the 7 upper bits stored in the first byte (with the high bit set to 1)
|
|
* and the 7 lower bits in the second byte.
|
|
*/
|
|
#endif
|
|
|
|
/*
|
|
* print the content of a macro, in #define form
|
|
*/
|
|
static void print_macro(void *vm)
|
|
{
|
|
struct macro *m = vm;
|
|
char *mname = HASH_ITEM_NAME(m);
|
|
int x = check_special_macro(mname);
|
|
size_t i;
|
|
|
|
if (x != MAC_NONE) {
|
|
fprintf(emit_output, "/* #define %s */ /* special */\n",
|
|
mname);
|
|
return;
|
|
}
|
|
fprintf(emit_output, "#define %s", mname);
|
|
if (m->narg >= 0) {
|
|
fprintf(emit_output, "(");
|
|
for (i = 0; i < (size_t)(m->narg); i ++) {
|
|
fprintf(emit_output, i ? ", %s" : "%s", m->arg[i]);
|
|
}
|
|
if (m->vaarg) {
|
|
fputs(m->narg ? ", ..." : "...", emit_output);
|
|
}
|
|
fprintf(emit_output, ")");
|
|
}
|
|
#ifdef LOW_MEM
|
|
if (m->cval.length == 0) {
|
|
fputc('\n', emit_output);
|
|
return;
|
|
}
|
|
fputc(' ', emit_output);
|
|
for (i = 0; i < m->cval.length;) {
|
|
int tt = m->cval.t[i ++];
|
|
|
|
if (tt == MACROARG) {
|
|
unsigned anum = m->cval.t[i];
|
|
|
|
if (anum >= 128) anum = ((anum & 127U) << 8)
|
|
| m->cval.t[++ i];
|
|
if (anum == (unsigned)m->narg)
|
|
fputs("__VA_ARGS__", emit_output);
|
|
else
|
|
fputs(m->arg[anum], emit_output);
|
|
i ++;
|
|
}
|
|
else if (S_TOKEN(tt)) {
|
|
fputs((char *)(m->cval.t + i), emit_output);
|
|
i += 1 + strlen((char *)(m->cval.t + i));
|
|
} else fputs(operators_name[tt], emit_output);
|
|
}
|
|
#else
|
|
if (m->val.nt == 0) {
|
|
fputc('\n', emit_output);
|
|
return;
|
|
}
|
|
fputc(' ', emit_output);
|
|
for (i = 0; i < m->val.nt; i ++) {
|
|
if (m->val.t[i].type == MACROARG) {
|
|
if (m->val.t[i].line == m->narg)
|
|
fputs("__VA_ARGS__", emit_output);
|
|
else
|
|
fputs(m->arg[(size_t)(m->val.t[i].line)],
|
|
emit_output);
|
|
} else fputs(token_name(m->val.t + i), emit_output);
|
|
}
|
|
#endif
|
|
fputc('\n', emit_output);
|
|
}
|
|
|
|
/*
|
|
* Send a token to the output (a token_fifo in lexer mode, the output
|
|
* buffer in stand alone mode).
|
|
*/
|
|
void print_token(struct lexer_state *ls, struct token *t, long uz_line)
|
|
{
|
|
char *x = t->name;
|
|
|
|
if (uz_line && t->line < 0) t->line = uz_line;
|
|
if (ls->flags & LEXER) {
|
|
struct token at;
|
|
|
|
at = *t;
|
|
if (S_TOKEN(t->type)) {
|
|
at.name = sdup(at.name);
|
|
throw_away(ls->gf, at.name);
|
|
}
|
|
aol(ls->output_fifo->t, ls->output_fifo->nt, at,
|
|
TOKEN_LIST_MEMG);
|
|
return;
|
|
}
|
|
if (ls->flags & KEEP_OUTPUT) {
|
|
for (; ls->oline < ls->line;) put_char(ls, '\n');
|
|
}
|
|
if (!S_TOKEN(t->type)) x = operators_name[t->type];
|
|
for (; *x; x ++) put_char(ls, *x);
|
|
}
|
|
|
|
/*
|
|
* Send a token to the output at a given line (this is for text output
|
|
* and unreplaced macros due to lack of arguments).
|
|
*/
|
|
static void print_token_nailed(struct lexer_state *ls, struct token *t,
|
|
long nail_line)
|
|
{
|
|
char *x = t->name;
|
|
|
|
if (ls->flags & LEXER) {
|
|
print_token(ls, t, 0);
|
|
return;
|
|
}
|
|
if (ls->flags & KEEP_OUTPUT) {
|
|
for (; ls->oline < nail_line;) put_char(ls, '\n');
|
|
}
|
|
if (!S_TOKEN(t->type)) x = operators_name[t->type];
|
|
for (; *x; x ++) put_char(ls, *x);
|
|
}
|
|
|
|
/*
|
|
* send a reduced whitespace token to the output
|
|
*/
|
|
#define print_space(ls) do { \
|
|
struct token lt; \
|
|
lt.type = OPT_NONE; \
|
|
lt.line = (ls)->line; \
|
|
print_token((ls), <, 0); \
|
|
} while (0)
|
|
|
|
/*
|
|
* We found a #define directive; parse the end of the line, perform
|
|
* sanity checks, store the new macro into the "macros" hash table.
|
|
*
|
|
* In case of a redefinition of a macro: we enforce the rule that a
|
|
* macro should be redefined identically, including the spelling of
|
|
* parameters. We emit an error on offending code; dura lex, sed lex.
|
|
* After all, it is easy to avoid such problems, with a #undef directive.
|
|
*/
|
|
int handle_define(struct lexer_state *ls)
|
|
{
|
|
struct macro *m = 0, *n;
|
|
#ifdef LOW_MEM
|
|
struct token_fifo mv;
|
|
#endif
|
|
int ltwws = 1, redef = 0;
|
|
char *mname = 0;
|
|
int narg;
|
|
size_t nt;
|
|
long l = ls->line;
|
|
|
|
#ifdef LOW_MEM
|
|
mv.art = mv.nt = 0;
|
|
#endif
|
|
/* find the next non-white token on the line, this should be
|
|
the macro name */
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE) {
|
|
if (ttMWS(ls->ctok->type)) continue;
|
|
if (ls->ctok->type == NAME) mname = sdup(ls->ctok->name);
|
|
break;
|
|
}
|
|
if (mname == 0) {
|
|
error(l, "missing macro name");
|
|
return 1;
|
|
}
|
|
if (check_special_macro(mname)) {
|
|
error(l, "trying to redefine the special macro %s", mname);
|
|
goto warp_error;
|
|
}
|
|
/*
|
|
* If a macro with this name was already defined: the K&R
|
|
* states that the new macro should be identical to the old one
|
|
* (with some arcane rule of equivalence of whitespace); otherwise,
|
|
* redefining the macro is an error. Most preprocessors would
|
|
* only emit a warning (or nothing at all) on an unidentical
|
|
* redefinition.
|
|
*
|
|
* Since it is easy to avoid this error (with a #undef directive),
|
|
* we choose to enforce the rule and emit an error.
|
|
*/
|
|
if ((n = HTT_get(¯os, mname)) != 0) {
|
|
/* redefinition of a macro: we must check that we define
|
|
it identical */
|
|
redef = 1;
|
|
#ifdef LOW_MEM
|
|
n->cval.rp = 0;
|
|
#endif
|
|
freemem(mname);
|
|
mname = 0;
|
|
}
|
|
if (!redef) {
|
|
m = new_macro();
|
|
m->narg = -1;
|
|
#ifdef LOW_MEM
|
|
#define mval mv
|
|
#else
|
|
#define mval (m->val)
|
|
#endif
|
|
}
|
|
if (next_token(ls)) goto define_end;
|
|
/*
|
|
* Check if the token immediately following the macro name is
|
|
* a left parenthesis; if so, then this is a macro with arguments.
|
|
* Collect their names and try to match the next parenthesis.
|
|
*/
|
|
if (ls->ctok->type == LPAR) {
|
|
int i, j;
|
|
int need_comma = 0, saw_mdots = 0;
|
|
|
|
narg = 0;
|
|
while (!next_token(ls)) {
|
|
if (ls->ctok->type == NEWLINE) {
|
|
error(l, "truncated macro definition");
|
|
goto define_error;
|
|
}
|
|
if (ls->ctok->type == COMMA) {
|
|
if (saw_mdots) {
|
|
error(l, "'...' must end the macro "
|
|
"argument list");
|
|
goto warp_error;
|
|
}
|
|
if (!need_comma) {
|
|
error(l, "void macro argument");
|
|
goto warp_error;
|
|
}
|
|
need_comma = 0;
|
|
continue;
|
|
} else if (ls->ctok->type == NAME) {
|
|
if (saw_mdots) {
|
|
error(l, "'...' must end the macro "
|
|
"argument list");
|
|
goto warp_error;
|
|
}
|
|
if (need_comma) {
|
|
error(l, "missing comma in "
|
|
"macro argument list");
|
|
goto warp_error;
|
|
}
|
|
if (!redef) {
|
|
aol(m->arg, narg,
|
|
sdup(ls->ctok->name), 8);
|
|
/* we must keep track of m->narg
|
|
so that cleanup in case of
|
|
error works. */
|
|
m->narg = narg;
|
|
if (narg == 128
|
|
&& (ls->flags & WARN_STANDARD))
|
|
warning(l, "more arguments to "
|
|
"macro than the ISO "
|
|
"limit (127)");
|
|
#ifdef LOW_MEM
|
|
if (narg == 32767) {
|
|
error(l, "too many arguments "
|
|
"in macro definition "
|
|
"(max 32766)");
|
|
goto warp_error;
|
|
}
|
|
#endif
|
|
} else {
|
|
/* this is a redefinition of the
|
|
macro; check equality between
|
|
old and new definitions */
|
|
if (narg >= n->narg) goto redef_error;
|
|
if (strcmp(ls->ctok->name,
|
|
n->arg[narg ++]))
|
|
goto redef_error;
|
|
}
|
|
need_comma = 1;
|
|
continue;
|
|
} else if ((ls->flags & MACRO_VAARG)
|
|
&& ls->ctok->type == MDOTS) {
|
|
if (need_comma) {
|
|
error(l, "missing comma before '...'");
|
|
goto warp_error;
|
|
}
|
|
if (redef && !n->vaarg) goto redef_error;
|
|
if (!redef) m->vaarg = 1;
|
|
saw_mdots = 1;
|
|
need_comma = 1;
|
|
continue;
|
|
} else if (ls->ctok->type == RPAR) {
|
|
if (narg > 0 && !need_comma) {
|
|
error(l, "void macro argument");
|
|
goto warp_error;
|
|
}
|
|
if (redef && n->vaarg && !saw_mdots)
|
|
goto redef_error;
|
|
break;
|
|
} else if (ttMWS(ls->ctok->type)) {
|
|
continue;
|
|
}
|
|
error(l, "invalid macro argument");
|
|
goto warp_error;
|
|
}
|
|
if (!redef) {
|
|
for (i = 1; i < narg; i ++) for (j = 0; j < i; j ++)
|
|
if (!strcmp(m->arg[i], m->arg[j])) {
|
|
error(l, "duplicate macro "
|
|
"argument");
|
|
goto warp_error;
|
|
}
|
|
}
|
|
if (!redef) m->narg = narg;
|
|
} else {
|
|
if (!ttWHI(ls->ctok->type) && (ls->flags & WARN_STANDARD))
|
|
warning(ls->line, "identifier not followed by "
|
|
"whitespace in #define");
|
|
ls->flags |= READ_AGAIN;
|
|
narg = 0;
|
|
}
|
|
if (redef) nt = 0;
|
|
|
|
/* now, we have the arguments. Let's get the macro contents. */
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE) {
|
|
struct token t;
|
|
|
|
t.type = ls->ctok->type;
|
|
if (ltwws && ttMWS(t.type)) continue;
|
|
t.line = 0;
|
|
if (t.type == NAME) {
|
|
int i;
|
|
|
|
if ((ls->flags & MACRO_VAARG)
|
|
&& !strcmp(ls->ctok->name, "__VA_ARGS__")) {
|
|
if (redef) {
|
|
if (!n->vaarg) goto redef_error;
|
|
} else if (!m->vaarg) {
|
|
error(l, "'__VA_ARGS__' is forbidden "
|
|
"in macros with a fixed "
|
|
"number of arguments");
|
|
goto warp_error;
|
|
}
|
|
t.type = MACROARG;
|
|
t.line = redef ? n->narg : m->narg;
|
|
}
|
|
for (i = 0; i < narg; i ++)
|
|
if (!strcmp(redef ? n->arg[i] : m->arg[i],
|
|
ls->ctok->name)) {
|
|
t.type = MACROARG;
|
|
/* this is a hack: we store the
|
|
argument number in the line field */
|
|
t.line = i;
|
|
break;
|
|
}
|
|
}
|
|
if (!redef && S_TOKEN(t.type)) t.name = sdup(ls->ctok->name);
|
|
if (ttMWS(t.type)) {
|
|
if (ltwws) continue;
|
|
#ifdef SEMPER_FIDELIS
|
|
t.type = OPT_NONE;
|
|
#else
|
|
t.type = NONE;
|
|
#endif
|
|
ltwws = 1;
|
|
} else ltwws = 0;
|
|
if (!redef) {
|
|
/* we ensure that each macro token has a correct
|
|
line number */
|
|
if (t.type != MACROARG) t.line = 1;
|
|
aol(mval.t, mval.nt, t, TOKEN_LIST_MEMG);
|
|
} else {
|
|
#ifdef LOW_MEM
|
|
int tt;
|
|
|
|
if (n->cval.rp >= n->cval.length) {
|
|
#ifdef SEMPER_FIDELIS
|
|
if (t.type != OPT_NONE) goto redef_error;
|
|
#else
|
|
if (t.type != NONE) goto redef_error;
|
|
#endif
|
|
} else if (t.type != n->cval.t[n->cval.rp]) {
|
|
goto redef_error;
|
|
} else if (t.type == MACROARG) {
|
|
unsigned anum = n->cval.t[n->cval.rp + 1];
|
|
|
|
if (anum >= 128U) anum = ((anum & 127U) << 8)
|
|
| m->cval.t[n->cval.rp + 2];
|
|
if (anum != (unsigned)t.line) goto redef_error;
|
|
} else if (S_TOKEN(t.type) && strcmp(ls->ctok->name,
|
|
(char *)(n->cval.t + n->cval.rp + 1))) {
|
|
goto redef_error;
|
|
}
|
|
tt = n->cval.t[n->cval.rp ++];
|
|
if (S_TOKEN(tt)) n->cval.rp += 1
|
|
+ strlen((char *)(n->cval.t + n->cval.rp));
|
|
else if (tt == MACROARG) {
|
|
if (n->cval.t[++ n->cval.rp] >= 128)
|
|
n->cval.rp ++;
|
|
}
|
|
#else
|
|
if (nt >= n->val.nt) {
|
|
#ifdef SEMPER_FIDELIS
|
|
if (t.type != OPT_NONE) goto redef_error;
|
|
#else
|
|
if (t.type != NONE) goto redef_error;
|
|
#endif
|
|
} else if (t.type != n->val.t[nt].type
|
|
|| (t.type == MACROARG
|
|
&& t.line != n->val.t[nt].line)
|
|
|| (S_TOKEN(t.type) && strcmp(ls->ctok->name,
|
|
n->val.t[nt].name))) {
|
|
goto redef_error;
|
|
}
|
|
#endif
|
|
nt ++;
|
|
}
|
|
}
|
|
|
|
if (redef) {
|
|
#ifdef LOW_MEM
|
|
if (n->cval.rp < n->cval.length) goto redef_error_2;
|
|
#else
|
|
if (nt < n->val.nt) goto redef_error_2;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* now we have the complete macro; perform some checks about
|
|
the operators # and ##, and, if everything is ok,
|
|
store the macro into the hash table */
|
|
define_end:
|
|
#ifdef SEMPER_FIDELIS
|
|
if (mval.nt && mval.t[mval.nt - 1].type == OPT_NONE) {
|
|
#else
|
|
if (mval.nt && mval.t[mval.nt - 1].type == NONE) {
|
|
#endif
|
|
mval.nt --;
|
|
if (mval.nt == 0) freemem(mval.t);
|
|
}
|
|
if (mval.nt != 0) {
|
|
size_t i;
|
|
|
|
/* some checks about the macro */
|
|
if (mval.t[0].type == DSHARP
|
|
|| mval.t[0].type == DIG_DSHARP
|
|
|| mval.t[mval.nt - 1].type == DSHARP
|
|
|| mval.t[mval.nt - 1].type == DIG_DSHARP) {
|
|
error(l, "operator '##' may neither begin "
|
|
"nor end a macro");
|
|
goto define_error;
|
|
}
|
|
if (m->narg >= 0) for (i = 0; i < mval.nt; i ++)
|
|
if ((mval.t[i].type == SHARP
|
|
|| mval.t[i].type == DIG_SHARP) &&
|
|
(i == (mval.nt - 1)
|
|
|| (ttMWS(mval.t[i + 1].type) &&
|
|
(i == mval.nt - 2
|
|
|| mval.t[i + 2].type != MACROARG))
|
|
|| (!ttMWS(mval.t[i + 1].type)
|
|
&& mval.t[i + 1].type != MACROARG))) {
|
|
error(l, "operator '#' not followed "
|
|
"by a macro argument");
|
|
goto define_error;
|
|
}
|
|
}
|
|
#ifdef LOW_MEM
|
|
{
|
|
size_t i, l;
|
|
|
|
for (i = 0, l = 0; i < mval.nt; i ++) {
|
|
l ++;
|
|
if (S_TOKEN(mval.t[i].type))
|
|
l += 1 + strlen(mval.t[i].name);
|
|
else if (mval.t[i].type == MACROARG) {
|
|
l ++;
|
|
if (mval.t[i].line >= 128) l ++;
|
|
}
|
|
}
|
|
m->cval.length = l;
|
|
if (l) m->cval.t = getmem(l);
|
|
for (i = 0, l = 0; i < mval.nt; i ++) {
|
|
m->cval.t[l ++] = mval.t[i].type;
|
|
if (S_TOKEN(mval.t[i].type)) {
|
|
size_t x = 1 + strlen(mval.t[i].name);
|
|
|
|
mmv(m->cval.t + l, mval.t[i].name, x);
|
|
l += x;
|
|
freemem(mval.t[i].name);
|
|
}
|
|
else if (mval.t[i].type == MACROARG) {
|
|
unsigned anum = mval.t[i].line;
|
|
|
|
if (anum >= 128) {
|
|
m->cval.t[l ++] = 128 | (anum >> 8);
|
|
m->cval.t[l ++] = anum & 0xFF;
|
|
} else {
|
|
m->cval.t[l ++] = anum;
|
|
}
|
|
}
|
|
}
|
|
if (mval.nt) freemem(mval.t);
|
|
}
|
|
#endif
|
|
HTT_put(¯os, m, mname);
|
|
freemem(mname);
|
|
if (emit_defines) print_macro(m);
|
|
return 0;
|
|
|
|
redef_error:
|
|
while (ls->ctok->type != NEWLINE && !next_token(ls));
|
|
redef_error_2:
|
|
error(l, "macro '%s' redefined unidentically", HASH_ITEM_NAME(n));
|
|
return 1;
|
|
warp_error:
|
|
while (ls->ctok->type != NEWLINE && !next_token(ls));
|
|
define_error:
|
|
if (m) del_macro(m);
|
|
if (mname) freemem(mname);
|
|
#ifdef LOW_MEM
|
|
if (mv.nt) {
|
|
size_t i;
|
|
|
|
for (i = 0; i < mv.nt; i ++)
|
|
if (S_TOKEN(mv.t[i].type)) freemem(mv.t[i].name);
|
|
freemem(mv.t);
|
|
}
|
|
#endif
|
|
return 1;
|
|
#undef mval
|
|
}
|
|
|
|
/*
|
|
* Get the arguments for a macro. This code is tricky because there can
|
|
* be multiple sources for these arguments, if we are in the middle of
|
|
* a macro replacement; arguments are macro-replaced before inclusion
|
|
* into the macro replacement.
|
|
*
|
|
* return value:
|
|
* 1 no argument (last token read from next_token())
|
|
* 2 no argument (last token read from tfi)
|
|
* 3 no argument (nothing read)
|
|
* 4 error
|
|
*
|
|
* Void arguments are allowed in C99.
|
|
*/
|
|
static int collect_arguments(struct lexer_state *ls, struct token_fifo *tfi,
|
|
int penury, struct token_fifo *atl, int narg, int vaarg, int *wr)
|
|
{
|
|
int ltwws = 1, npar = 0, i;
|
|
struct token *ct = 0;
|
|
int read_from_fifo = 0;
|
|
long begin_line = ls->line;
|
|
|
|
#define unravel(ls) (read_from_fifo = 0, !((tfi && tfi->art < tfi->nt \
|
|
&& (read_from_fifo = 1) != 0 && (ct = tfi->t + (tfi->art ++))) \
|
|
|| ((!tfi || penury) && !next_token(ls) && (ct = (ls)->ctok))))
|
|
|
|
/*
|
|
* collect_arguments() is assumed to setup correctly atl
|
|
* (this is not elegant, but it works)
|
|
*/
|
|
for (i = 0; i < narg; i ++) atl[i].art = atl[i].nt = 0;
|
|
if (vaarg) atl[narg].art = atl[narg].nt = 0;
|
|
*wr = 0;
|
|
while (!unravel(ls)) {
|
|
if (!read_from_fifo && ct->type == NEWLINE) ls->ltwnl = 1;
|
|
if (ttWHI(ct->type)) {
|
|
*wr = 1;
|
|
continue;
|
|
}
|
|
if (ct->type == LPAR) {
|
|
npar = 1;
|
|
}
|
|
break;
|
|
}
|
|
if (!npar) {
|
|
if (ct == ls->ctok) return 1;
|
|
if (read_from_fifo) return 2;
|
|
return 3;
|
|
}
|
|
if (!read_from_fifo && ct == ls->ctok) ls->ltwnl = 0;
|
|
i = 0;
|
|
if ((narg + vaarg) == 0) {
|
|
while(!unravel(ls)) {
|
|
if (ttWHI(ct->type)) continue;
|
|
if (ct->type == RPAR) goto harvested;
|
|
npar = 1;
|
|
goto too_many_args;
|
|
}
|
|
}
|
|
while (!unravel(ls)) {
|
|
struct token t;
|
|
|
|
if (ct->type == LPAR) npar ++;
|
|
else if (ct->type == RPAR && (-- npar) == 0) {
|
|
if (atl[i].nt != 0
|
|
&& ttMWS(atl[i].t[atl[i].nt - 1].type))
|
|
atl[i].nt --;
|
|
i ++;
|
|
/*
|
|
* C99 standard states that at least one argument
|
|
* should be present for the ... part; to relax
|
|
* this behaviour, change 'narg + vaarg' to 'narg'.
|
|
*/
|
|
if (i < (narg + vaarg)) {
|
|
error(begin_line, "not enough arguments "
|
|
"to macro");
|
|
return 4;
|
|
}
|
|
if (i > narg) {
|
|
if (!(ls->flags & MACRO_VAARG) || !vaarg)
|
|
goto too_many_args;
|
|
}
|
|
goto harvested;
|
|
} else if (ct->type == COMMA && npar <= 1 && i < narg) {
|
|
if (atl[i].nt != 0
|
|
&& ttMWS(atl[i].t[atl[i].nt - 1].type))
|
|
atl[i].nt --;
|
|
if (++ i == narg) {
|
|
if (!(ls->flags & MACRO_VAARG) || !vaarg)
|
|
goto too_many_args;
|
|
}
|
|
if (i > 30000) goto too_many_args;
|
|
ltwws = 1;
|
|
continue;
|
|
} else if (ltwws && ttWHI(ct->type)) continue;
|
|
|
|
t.type = ct->type;
|
|
if (!read_from_fifo) t.line = ls->line; else t.line = ct->line;
|
|
/*
|
|
* Stringification applies only to macro arguments;
|
|
* so we handle here OPT_NONE.
|
|
* OPT_NONE is kept, but does not count as whitespace,
|
|
* and merges with other whitespace to give a fully
|
|
* qualified NONE token. Two OPT_NONE tokens merge.
|
|
* Initial and final OPT_NONE are discarded (initial
|
|
* is already done, as OPT_NONE is matched by ttWHI).
|
|
*/
|
|
if (ttWHI(t.type)) {
|
|
if (t.type != OPT_NONE) {
|
|
t.type = NONE;
|
|
#ifdef SEMPER_FIDELIS
|
|
t.name = sdup(" ");
|
|
throw_away(ls->gf, t.name);
|
|
#endif
|
|
ltwws = 1;
|
|
}
|
|
if (atl[i].nt > 0
|
|
&& atl[i].t[atl[i].nt - 1].type == OPT_NONE)
|
|
atl[i].nt --;
|
|
} else {
|
|
ltwws = 0;
|
|
if (S_TOKEN(t.type)) {
|
|
t.name = ct->name;
|
|
if (ct == (ls)->ctok) {
|
|
t.name = sdup(t.name);
|
|
throw_away(ls->gf, t.name);
|
|
}
|
|
}
|
|
}
|
|
aol(atl[i].t, atl[i].nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
error(begin_line, "unfinished macro call");
|
|
return 4;
|
|
too_many_args:
|
|
error(begin_line, "too many arguments to macro");
|
|
while (npar && !unravel(ls)) {
|
|
if (ct->type == LPAR) npar ++;
|
|
else if (ct->type == RPAR) npar --;
|
|
}
|
|
return 4;
|
|
harvested:
|
|
if (i > 127 && (ls->flags & WARN_STANDARD))
|
|
warning(begin_line, "macro call with %d arguments (ISO "
|
|
"specifies 127 max)", i);
|
|
return 0;
|
|
#undef unravel
|
|
}
|
|
|
|
/*
|
|
* concat_token() is called when the ## operator is used. It uses
|
|
* the struct lexer_state dsharp_lexer to parse the result of the
|
|
* concatenation.
|
|
*
|
|
* Law enforcement: if the whole string does not produce a valid
|
|
* single token, an error (non-zero result) is returned.
|
|
*/
|
|
struct lexer_state dsharp_lexer;
|
|
|
|
static inline int concat_token(struct token *t1, struct token *t2)
|
|
{
|
|
char *n1 = token_name(t1), *n2 = token_name(t2);
|
|
size_t l1 = strlen(n1), l2 = strlen(n2);
|
|
unsigned char *x = getmem(l1 + l2 + 1);
|
|
int r;
|
|
|
|
mmv(x, n1, l1);
|
|
mmv(x + l1, n2, l2);
|
|
x[l1 + l2] = 0;
|
|
dsharp_lexer.input = 0;
|
|
dsharp_lexer.input_string = x;
|
|
dsharp_lexer.pbuf = 0;
|
|
dsharp_lexer.ebuf = l1 + l2;
|
|
dsharp_lexer.discard = 1;
|
|
dsharp_lexer.flags = DEFAULT_LEXER_FLAGS;
|
|
dsharp_lexer.pending_token = 0;
|
|
r = next_token(&dsharp_lexer);
|
|
freemem(x);
|
|
return (r == 1 || dsharp_lexer.pbuf < (l1 + l2)
|
|
|| dsharp_lexer.pending_token
|
|
|| (dsharp_lexer.pbuf == (l1 + l2) && !dsharp_lexer.discard));
|
|
}
|
|
|
|
#ifdef PRAGMA_TOKENIZE
|
|
/*
|
|
* tokenize_string() takes a string as input, and split it into tokens,
|
|
* reassembling the tokens into a single compressed string generated by
|
|
* compress_token_list(); this function is used for _Pragma processing.
|
|
*/
|
|
struct lexer_state tokenize_lexer;
|
|
|
|
static char *tokenize_string(struct lexer_state *ls, char *buf)
|
|
{
|
|
struct token_fifo tf;
|
|
size_t bl = strlen(buf);
|
|
int r;
|
|
|
|
tokenize_lexer.input = 0;
|
|
tokenize_lexer.input_string = (unsigned char *)buf;
|
|
tokenize_lexer.pbuf = 0;
|
|
tokenize_lexer.ebuf = bl;
|
|
tokenize_lexer.discard = 1;
|
|
tokenize_lexer.flags = ls->flags | LEXER;
|
|
tokenize_lexer.pending_token = 0;
|
|
tf.art = tf.nt = 0;
|
|
while (!(r = next_token(&tokenize_lexer))) {
|
|
struct token t, *ct = tokenize_lexer.ctok;
|
|
|
|
if (ttWHI(ct->type)) continue;
|
|
t = *ct;
|
|
if (S_TOKEN(t.type)) t.name = sdup(t.name);
|
|
aol(tf.t, tf.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
if (tokenize_lexer.pbuf < bl) goto tokenize_error;
|
|
return (char *)((compress_token_list(&tf)).t);
|
|
|
|
tokenize_error:
|
|
if (tf.nt) {
|
|
for (tf.art = 0; tf.art < tf.nt; tf.art ++)
|
|
if (S_TOKEN(tf.t[tf.art].type))
|
|
freemem(tf.t[tf.art].name);
|
|
freemem(tf.t);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* stringify_string() has a self-explanatory name. It is called when
|
|
* the # operator is used in a macro and a string constant must be
|
|
* stringified.
|
|
*/
|
|
static inline char *stringify_string(char *x)
|
|
{
|
|
size_t l;
|
|
int i, inside_str = 0, inside_cc = 0, must_quote, has_quoted = 0;
|
|
char *y, *d;
|
|
|
|
for (i = 0; i < 2; i ++) {
|
|
if (i) d[0] = '"';
|
|
for (l = 1, y = x; *y; y ++, l ++) {
|
|
must_quote = 0;
|
|
if (inside_cc) {
|
|
if (*y == '\\') {
|
|
must_quote = 1;
|
|
has_quoted = 1;
|
|
} else if (!has_quoted && *y == '\'')
|
|
inside_cc = 0;
|
|
} else if (inside_str) {
|
|
if (*y == '"' || *y == '\\') must_quote = 1;
|
|
if (*y == '\\') has_quoted = 1;
|
|
else if (!has_quoted && *y == '"')
|
|
inside_str = 0;
|
|
} else if (*y == '"') {
|
|
inside_str = 1;
|
|
must_quote = 1;
|
|
} else if (*y == '\'') {
|
|
inside_cc = 1;
|
|
}
|
|
if (must_quote) {
|
|
if (i) d[l] = '\\';
|
|
l ++;
|
|
}
|
|
if (i) d[l] = *y;
|
|
}
|
|
if (!i) d = getmem(l + 2);
|
|
if (i) {
|
|
d[l] = '"';
|
|
d[l + 1] = 0;
|
|
}
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/*
|
|
* stringify() produces a constant string, result of the # operator
|
|
* on a list of tokens.
|
|
*/
|
|
static char *stringify(struct token_fifo *tf)
|
|
{
|
|
size_t tlen;
|
|
size_t i;
|
|
char *x, *y;
|
|
|
|
for (tlen = 0, i = 0; i < tf->nt; i ++)
|
|
if (tf->t[i].type < CPPERR && tf->t[i].type != OPT_NONE)
|
|
tlen += strlen(token_name(tf->t + i));
|
|
if (tlen == 0) return sdup("\"\"");
|
|
x = getmem(tlen + 1);
|
|
for (tlen = 0, i = 0; i < tf->nt; i ++) {
|
|
if (tf->t[i].type >= CPPERR || tf->t[i].type == OPT_NONE)
|
|
continue;
|
|
strcpy(x + tlen, token_name(tf->t + i));
|
|
tlen += strlen(token_name(tf->t + i));
|
|
}
|
|
/* no need to add a trailing 0: strcpy() did that (and the string
|
|
is not empty) */
|
|
y = stringify_string(x);
|
|
freemem(x);
|
|
return y;
|
|
}
|
|
|
|
/*
|
|
* Two strings evaluated at initialization time, to handle the __TIME__
|
|
* and __DATE__ special macros.
|
|
*
|
|
* C99 specifies that these macros should remain constant throughout
|
|
* the whole preprocessing.
|
|
*/
|
|
char compile_time[12], compile_date[24];
|
|
|
|
/*
|
|
* substitute_macro() performs the macro substitution. It is called when
|
|
* an identifier recognized as a macro name has been found; this function
|
|
* tries to collect the arguments (if needed), applies # and ## operators
|
|
* and perform recursive and nested macro expansions.
|
|
*
|
|
* In the substitution of a macro, we remove all newlines that were in the
|
|
* arguments. This might confuse error reporting (which could report
|
|
* erroneous line numbers) or have worse effect is the preprocessor is
|
|
* used for another language pickier than C. Since the interface between
|
|
* the preprocessor and the compiler is not fully specified, I believe
|
|
* that this is no violation of the standard. Comments welcome.
|
|
*
|
|
* We take tokens from tfi. If tfi has no more tokens to give: we may
|
|
* take some tokens from ls to complete a call (fetch arguments) if
|
|
* and only if penury is non zero.
|
|
*/
|
|
int substitute_macro(struct lexer_state *ls, struct macro *m,
|
|
struct token_fifo *tfi, int penury, int reject_nested, long l)
|
|
{
|
|
char *mname = HASH_ITEM_NAME(m);
|
|
struct token_fifo *atl, etl;
|
|
struct token t, *ct;
|
|
int i, save_nest = m->nest;
|
|
size_t save_art, save_tfi, etl_limit;
|
|
int ltwds, ntwds, ltwws;
|
|
int pragma_op = 0;
|
|
|
|
/*
|
|
* Reject the replacement, if we are already inside the macro.
|
|
*/
|
|
if (m->nest > reject_nested) {
|
|
t.type = NAME;
|
|
t.line = ls->line;
|
|
t.name = mname;
|
|
print_token(ls, &t, 0);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* put a separation from preceeding tokens
|
|
*/
|
|
print_space(ls);
|
|
|
|
/*
|
|
* Check if the macro is a special one.
|
|
*/
|
|
if ((i = check_special_macro(mname)) >= MAC_SPECIAL) {
|
|
/* we have a special macro */
|
|
switch (i) {
|
|
char buf[30], *bbuf, *cfn;
|
|
|
|
case MAC_LINE:
|
|
t.type = NUMBER;
|
|
t.line = l;
|
|
sprintf(buf, "%ld", l);
|
|
t.name = buf;
|
|
print_space(ls);
|
|
print_token(ls, &t, 0);
|
|
break;
|
|
case MAC_FILE:
|
|
t.type = STRING;
|
|
t.line = l;
|
|
cfn = current_long_filename ?
|
|
current_long_filename : current_filename;
|
|
bbuf = getmem(2 * strlen(cfn) + 3);
|
|
{
|
|
char *c, *d;
|
|
int lcwb = 0;
|
|
|
|
bbuf[0] = '"';
|
|
for (c = cfn, d = bbuf + 1; *c; c ++) {
|
|
if (*c == '\\') {
|
|
if (lcwb) continue;
|
|
*(d ++) = '\\';
|
|
lcwb = 1;
|
|
} else lcwb = 0;
|
|
*(d ++) = *c;
|
|
}
|
|
*(d ++) = '"';
|
|
*(d ++) = 0;
|
|
}
|
|
t.name = bbuf;
|
|
print_space(ls);
|
|
print_token(ls, &t, 0);
|
|
freemem(bbuf);
|
|
break;
|
|
case MAC_DATE:
|
|
t.type = STRING;
|
|
t.line = l;
|
|
t.name = compile_date;
|
|
print_space(ls);
|
|
print_token(ls, &t, 0);
|
|
break;
|
|
case MAC_TIME:
|
|
t.type = STRING;
|
|
t.line = l;
|
|
t.name = compile_time;
|
|
print_space(ls);
|
|
print_token(ls, &t, 0);
|
|
break;
|
|
case MAC_STDC:
|
|
t.type = NUMBER;
|
|
t.line = l;
|
|
t.name = "1";
|
|
print_space(ls);
|
|
print_token(ls, &t, 0);
|
|
break;
|
|
case MAC_PRAGMA:
|
|
if (reject_nested > 0) {
|
|
/* do not replace _Pragma() unless toplevel */
|
|
t.type = NAME;
|
|
t.line = ls->line;
|
|
t.name = mname;
|
|
print_token(ls, &t, 0);
|
|
return 0;
|
|
}
|
|
pragma_op = 1;
|
|
goto collect_args;
|
|
#ifdef AUDIT
|
|
default:
|
|
ouch("unbekanntes fliegendes macro");
|
|
#endif
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* If the macro has arguments, collect them.
|
|
*/
|
|
collect_args:
|
|
if (m->narg >= 0) {
|
|
unsigned long save_flags = ls->flags;
|
|
int wr = 0;
|
|
|
|
ls->flags |= LEXER;
|
|
if (m->narg > 0 || m->vaarg)
|
|
atl = getmem((m->narg + m->vaarg)
|
|
* sizeof(struct token_fifo));
|
|
switch (collect_arguments(ls, tfi, penury, atl,
|
|
m->narg, m->vaarg, &wr)) {
|
|
case 1:
|
|
/* the macro expected arguments, but we did not
|
|
find any; the last read token should be read
|
|
again. */
|
|
ls->flags = save_flags | READ_AGAIN;
|
|
goto no_argument_next;
|
|
case 2:
|
|
tfi->art --;
|
|
/* fall through */
|
|
case 3:
|
|
ls->flags = save_flags;
|
|
no_argument_next:
|
|
t.type = NAME;
|
|
t.line = l;
|
|
t.name = mname;
|
|
print_token_nailed(ls, &t, l);
|
|
if (wr) {
|
|
t.type = NONE;
|
|
t.line = l;
|
|
#ifdef SEMPER_FIDELIS
|
|
t.name = " ";
|
|
#endif
|
|
print_token(ls, &t, 0);
|
|
goto exit_macro_2;
|
|
}
|
|
goto exit_macro_1;
|
|
case 4:
|
|
ls->flags = save_flags;
|
|
goto exit_error_1;
|
|
}
|
|
ls->flags = save_flags;
|
|
}
|
|
|
|
/*
|
|
* If the macro is _Pragma, and we got here, then we have
|
|
* exactly one argument. We check it, unstringize it, and
|
|
* emit a PRAGMA token.
|
|
*/
|
|
if (pragma_op) {
|
|
char *pn;
|
|
|
|
if (atl[0].nt != 1 || atl[0].t[0].type != STRING) {
|
|
error(ls->line, "invalid argument to _Pragma");
|
|
if (atl[0].nt) freemem(atl[0].t);
|
|
freemem(atl);
|
|
goto exit_error;
|
|
}
|
|
pn = atl[0].t[0].name;
|
|
if ((pn[0] == '"' && pn[1] == '"') || (pn[0] == 'L'
|
|
&& pn[1] == '"' && pn[2] == '"')) {
|
|
/* void pragma -- just ignore it */
|
|
freemem(atl[0].t);
|
|
freemem(atl);
|
|
return 0;
|
|
}
|
|
if (ls->flags & TEXT_OUTPUT) {
|
|
#ifdef PRAGMA_DUMP
|
|
/*
|
|
* This code works because we actually evaluate arguments in a
|
|
* lazy way: we scan a macro argument only if it appears in the
|
|
* output, and exactly as many times as it appears. Therefore,
|
|
* _Pragma() will get evaluated just like they should.
|
|
*/
|
|
char *c = atl[0].t[0].name, *d;
|
|
|
|
for (d = "\n#pragma "; *d; d ++) put_char(ls, *d);
|
|
d = (*c == 'L') ? c + 2 : c + 1;
|
|
for (; *d != '"'; d ++) {
|
|
if (*d == '\\' && (*(d + 1) == '\\'
|
|
|| *(d + 1) == '"')) {
|
|
d ++;
|
|
}
|
|
put_char(ls, *d);
|
|
}
|
|
put_char(ls, '\n');
|
|
ls->oline = ls->line;
|
|
enter_file(ls, ls->flags);
|
|
#else
|
|
if (ls->flags & WARN_PRAGMA)
|
|
warning(ls->line,
|
|
"_Pragma() ignored and not dumped");
|
|
#endif
|
|
} else if (ls->flags & HANDLE_PRAGMA) {
|
|
char *c = atl[0].t[0].name, *d, *buf;
|
|
struct token t;
|
|
|
|
/* a wide string is a string */
|
|
if (*c == 'L') c ++;
|
|
c ++;
|
|
for (buf = d = getmem(strlen(c)); *c != '"'; c ++) {
|
|
if (*c == '\\' && (*(c + 1) == '\\'
|
|
|| *(c + 1) == '"')) {
|
|
*(d ++) = *(++ c);
|
|
} else *(d ++) = *c;
|
|
}
|
|
*d = 0;
|
|
t.type = PRAGMA;
|
|
t.line = ls->line;
|
|
#ifdef PRAGMA_TOKENIZE
|
|
t.name = tokenize_string(ls, buf);
|
|
freemem(buf);
|
|
buf = t.name;
|
|
if (!buf) {
|
|
freemem(atl[0].t);
|
|
freemem(atl);
|
|
goto exit_error;
|
|
}
|
|
#else
|
|
t.name = buf;
|
|
#endif
|
|
aol(ls->toplevel_of->t, ls->toplevel_of->nt,
|
|
t, TOKEN_LIST_MEMG);
|
|
throw_away(ls->gf, buf);
|
|
}
|
|
freemem(atl[0].t);
|
|
freemem(atl);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Now we expand and replace the arguments in the macro; we
|
|
* also handle '#' and '##'. If we find an argument, that has
|
|
* to be replaced, we expand it in its own token list, then paste
|
|
* it. Tricky point: when we paste an argument, we must scan
|
|
* again the resulting list for further replacements. This
|
|
* implies problems with regards to nesting self-referencing
|
|
* macros.
|
|
*
|
|
* We do then YAUH (yet another ugly hack): if a macro is replaced,
|
|
* and nested replacement exhibit the same macro, we mark it with
|
|
* a negative line number. All produced negative line numbers
|
|
* must be cleaned in the end.
|
|
*/
|
|
|
|
#define ZAP_LINE(t) do { \
|
|
if ((t).type == NAME) { \
|
|
struct macro *zlm = HTT_get(¯os, (t).name); \
|
|
if (zlm && zlm->nest > reject_nested) \
|
|
(t).line = -1 - (t).line; \
|
|
} \
|
|
} while (0)
|
|
|
|
#ifdef LOW_MEM
|
|
save_art = m->cval.rp;
|
|
m->cval.rp = 0;
|
|
#else
|
|
save_art = m->val.art;
|
|
m->val.art = 0;
|
|
#endif
|
|
etl.art = etl.nt = 0;
|
|
m->nest = reject_nested + 1;
|
|
ltwds = ntwds = 0;
|
|
#ifdef LOW_MEM
|
|
while (m->cval.rp < m->cval.length) {
|
|
#else
|
|
while (m->val.art < m->val.nt) {
|
|
#endif
|
|
size_t next, z;
|
|
#ifdef LOW_MEM
|
|
struct token uu;
|
|
|
|
ct = &uu;
|
|
ct->line = 1;
|
|
t.type = ct->type = m->cval.t[m->cval.rp ++];
|
|
if (ct->type == MACROARG) {
|
|
unsigned anum = m->cval.t[m->cval.rp ++];
|
|
|
|
if (anum >= 128U) anum = ((anum & 127U) << 8)
|
|
| (unsigned)m->cval.t[m->cval.rp ++];
|
|
ct->line = anum;
|
|
} else if (S_TOKEN(ct->type)) {
|
|
t.name = ct->name = (char *)(m->cval.t + m->cval.rp);
|
|
m->cval.rp += 1 + strlen(ct->name);
|
|
}
|
|
#ifdef SEMPER_FIDELIS
|
|
else if (ct->type == OPT_NONE) {
|
|
t.type = ct->type = NONE;
|
|
t.name = ct->name = " ";
|
|
}
|
|
#endif
|
|
t.line = ls->line;
|
|
next = m->cval.rp;
|
|
if ((next < m->cval.length && (m->cval.t[z = next] == DSHARP
|
|
|| m->cval.t[z = next] == DIG_DSHARP))
|
|
|| ((next + 1) < m->cval.length
|
|
&& ttWHI(m->cval.t[next])
|
|
&& (m->cval.t[z = next + 1] == DSHARP
|
|
|| m->cval.t[z = next + 1] == DIG_DSHARP))) {
|
|
ntwds = 1;
|
|
m->cval.rp = z;
|
|
} else ntwds = 0;
|
|
#else
|
|
ct = m->val.t + (m->val.art ++);
|
|
next = m->val.art;
|
|
t.type = ct->type;
|
|
t.line = ls->line;
|
|
#ifdef SEMPER_FIDELIS
|
|
if (t.type == OPT_NONE) {
|
|
t.type = NONE;
|
|
t.name = " ";
|
|
} else
|
|
#endif
|
|
t.name = ct->name;
|
|
if ((next < m->val.nt && (m->val.t[z = next].type == DSHARP
|
|
|| m->val.t[z = next].type == DIG_DSHARP))
|
|
|| ((next + 1) < m->val.nt
|
|
&& ttWHI(m->val.t[next].type)
|
|
&& (m->val.t[z = next + 1].type == DSHARP
|
|
|| m->val.t[z = next + 1].type == DIG_DSHARP))) {
|
|
ntwds = 1;
|
|
m->val.art = z;
|
|
} else ntwds = 0;
|
|
#endif
|
|
if (ct->type == MACROARG) {
|
|
#ifdef DSHARP_TOKEN_MERGE
|
|
int need_opt_space = 1;
|
|
#endif
|
|
z = ct->line; /* the argument number is there */
|
|
if (ltwds && atl[z].nt != 0 && etl.nt) {
|
|
if (concat_token(etl.t + (-- etl.nt),
|
|
atl[z].t)) {
|
|
warning(ls->line, "operator '##' "
|
|
"produced the invalid token "
|
|
"'%s%s'",
|
|
token_name(etl.t + etl.nt),
|
|
token_name(atl[z].t));
|
|
#if 0
|
|
/* obsolete */
|
|
#ifdef LOW_MEM
|
|
m->cval.rp = save_art;
|
|
#else
|
|
m->val.art = save_art;
|
|
#endif
|
|
etl.nt ++;
|
|
goto exit_error_2;
|
|
#endif
|
|
etl.nt ++;
|
|
atl[z].art = 0;
|
|
#ifdef DSHARP_TOKEN_MERGE
|
|
need_opt_space = 0;
|
|
#endif
|
|
} else {
|
|
if (etl.nt == 0) freemem(etl.t);
|
|
else if (!ttWHI(etl.t[etl.nt - 1]
|
|
.type)) {
|
|
t.type = OPT_NONE;
|
|
t.line = ls->line;
|
|
aol(etl.t, etl.nt, t,
|
|
TOKEN_LIST_MEMG);
|
|
}
|
|
t.type = dsharp_lexer.ctok->type;
|
|
t.line = ls->line;
|
|
if (S_TOKEN(t.type)) {
|
|
t.name = sdup(dsharp_lexer
|
|
.ctok->name);
|
|
throw_away(ls->gf, t.name);
|
|
}
|
|
ZAP_LINE(t);
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
atl[z].art = 1;
|
|
}
|
|
} else atl[z].art = 0;
|
|
if (
|
|
#ifdef DSHARP_TOKEN_MERGE
|
|
need_opt_space &&
|
|
#endif
|
|
atl[z].art < atl[z].nt && (!etl.nt
|
|
|| !ttWHI(etl.t[etl.nt - 1].type))) {
|
|
t.type = OPT_NONE;
|
|
t.line = ls->line;
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
if (ltwds || ntwds) {
|
|
while (atl[z].art < atl[z].nt) {
|
|
t = atl[z].t[atl[z].art ++];
|
|
t.line = ls->line;
|
|
ZAP_LINE(t);
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
} else {
|
|
struct token_fifo *save_tf;
|
|
unsigned long save_flags;
|
|
int ret = 0;
|
|
|
|
atl[z].art = 0;
|
|
save_tf = ls->output_fifo;
|
|
ls->output_fifo = &etl;
|
|
save_flags = ls->flags;
|
|
ls->flags |= LEXER;
|
|
while (atl[z].art < atl[z].nt) {
|
|
struct macro *nm;
|
|
struct token *cct;
|
|
|
|
cct = atl[z].t + (atl[z].art ++);
|
|
if (cct->type == NAME
|
|
&& cct->line >= 0
|
|
&& (nm = HTT_get(¯os,
|
|
cct->name))
|
|
&& nm->nest <=
|
|
(reject_nested + 1)) {
|
|
ret |= substitute_macro(ls,
|
|
nm, atl + z, 0,
|
|
reject_nested + 1, l);
|
|
continue;
|
|
}
|
|
t = *cct;
|
|
ZAP_LINE(t);
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
ls->output_fifo = save_tf;
|
|
ls->flags = save_flags;
|
|
if (ret) {
|
|
#ifdef LOW_MEM
|
|
m->cval.rp = save_art;
|
|
#else
|
|
m->val.art = save_art;
|
|
#endif
|
|
goto exit_error_2;
|
|
}
|
|
}
|
|
if (!ntwds && (!etl.nt
|
|
|| !ttWHI(etl.t[etl.nt - 1].type))) {
|
|
t.type = OPT_NONE;
|
|
t.line = ls->line;
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
ltwds = 0;
|
|
continue;
|
|
}
|
|
/*
|
|
* This code is definitely cursed.
|
|
*
|
|
* For the extremely brave reader who tries to understand
|
|
* what is happening: ltwds is a flag meaning "last token
|
|
* was double-sharp" and ntwds means "next token will be
|
|
* double-sharp". The tokens are from the macro definition,
|
|
* and scanned from left to right. Arguments that are
|
|
* not implied into a #/## construction are macro-expanded
|
|
* seperately, then included into the token stream.
|
|
*/
|
|
if (ct->type == DSHARP || ct->type == DIG_DSHARP) {
|
|
if (ltwds) {
|
|
error(ls->line, "quad sharp");
|
|
#ifdef LOW_MEM
|
|
m->cval.rp = save_art;
|
|
#else
|
|
m->val.art = save_art;
|
|
#endif
|
|
goto exit_error_2;
|
|
}
|
|
#ifdef LOW_MEM
|
|
if (m->cval.rp < m->cval.length
|
|
&& ttMWS(m->cval.t[m->cval.rp]))
|
|
m->cval.rp ++;
|
|
#else
|
|
if (m->val.art < m->val.nt
|
|
&& ttMWS(m->val.t[m->val.art].type))
|
|
m->val.art ++;
|
|
#endif
|
|
ltwds = 1;
|
|
continue;
|
|
} else if (ltwds && etl.nt != 0) {
|
|
if (concat_token(etl.t + (-- etl.nt), ct)) {
|
|
warning(ls->line, "operator '##' produced "
|
|
"the invalid token '%s%s'",
|
|
token_name(etl.t + etl.nt),
|
|
token_name(ct));
|
|
#if 0
|
|
/* obsolete */
|
|
#ifdef LOW_MEM
|
|
m->cval.rp = save_art;
|
|
#else
|
|
m->val.art = save_art;
|
|
#endif
|
|
etl.nt ++;
|
|
goto exit_error_2;
|
|
#endif
|
|
etl.nt ++;
|
|
} else {
|
|
if (etl.nt == 0) freemem(etl.t);
|
|
t.type = dsharp_lexer.ctok->type;
|
|
t.line = ls->line;
|
|
if (S_TOKEN(t.type)) {
|
|
t.name = sdup(dsharp_lexer.ctok->name);
|
|
throw_away(ls->gf, t.name);
|
|
}
|
|
ct = &t;
|
|
}
|
|
}
|
|
ltwds = 0;
|
|
#ifdef LOW_MEM
|
|
if ((ct->type == SHARP || ct->type == DIG_SHARP)
|
|
&& next < m->cval.length
|
|
&& (m->cval.t[next] == MACROARG
|
|
|| (ttMWS(m->cval.t[next])
|
|
&& (next + 1) < m->cval.length
|
|
&& m->cval.t[next + 1] == MACROARG))) {
|
|
|
|
unsigned anum;
|
|
#else
|
|
if ((ct->type == SHARP || ct->type == DIG_SHARP)
|
|
&& next < m->val.nt
|
|
&& (m->val.t[next].type == MACROARG
|
|
|| (ttMWS(m->val.t[next].type)
|
|
&& (next + 1) < m->val.nt
|
|
&& m->val.t[next + 1].type == MACROARG))) {
|
|
#endif
|
|
/*
|
|
* We have a # operator followed by (an optional
|
|
* whitespace and) a macro argument; this means
|
|
* stringification. So be it.
|
|
*/
|
|
#ifdef LOW_MEM
|
|
if (ttMWS(m->cval.t[next])) m->cval.rp ++;
|
|
#else
|
|
if (ttMWS(m->val.t[next].type)) m->val.art ++;
|
|
#endif
|
|
t.type = STRING;
|
|
#ifdef LOW_MEM
|
|
anum = m->cval.t[++ m->cval.rp];
|
|
if (anum >= 128U) anum = ((anum & 127U) << 8)
|
|
| (unsigned)m->cval.t[++ m->cval.rp];
|
|
t.name = stringify(atl + anum);
|
|
m->cval.rp ++;
|
|
#else
|
|
t.name = stringify(atl +
|
|
(size_t)(m->val.t[m->val.art ++].line));
|
|
#endif
|
|
throw_away(ls->gf, t.name);
|
|
ct = &t;
|
|
/*
|
|
* There is no need for extra spaces here.
|
|
*/
|
|
}
|
|
t = *ct;
|
|
ZAP_LINE(t);
|
|
aol(etl.t, etl.nt, t, TOKEN_LIST_MEMG);
|
|
}
|
|
#ifdef LOW_MEM
|
|
m->cval.rp = save_art;
|
|
#else
|
|
m->val.art = save_art;
|
|
#endif
|
|
|
|
/*
|
|
* Now etl contains the expanded macro, to be parsed again for
|
|
* further expansions -- much easier, since '#' and '##' have
|
|
* already been handled.
|
|
* However, we might need some input from tfi. So, we paste
|
|
* the contents of tfi after etl, and we put back what was
|
|
* not used.
|
|
*
|
|
* Some adjacent spaces are merged; only unique NONE, or sequences
|
|
* OPT_NONE NONE are emitted.
|
|
*/
|
|
etl_limit = etl.nt;
|
|
if (tfi) {
|
|
save_tfi = tfi->art;
|
|
while (tfi->art < tfi->nt) aol(etl.t, etl.nt,
|
|
tfi->t[tfi->art ++], TOKEN_LIST_MEMG);
|
|
}
|
|
ltwws = 0;
|
|
while (etl.art < etl_limit) {
|
|
struct macro *nm;
|
|
|
|
ct = etl.t + (etl.art ++);
|
|
if (ct->type == NAME && ct->line >= 0
|
|
&& (nm = HTT_get(¯os, ct->name))) {
|
|
if (substitute_macro(ls, nm, &etl,
|
|
penury, reject_nested, l)) {
|
|
m->nest = save_nest;
|
|
goto exit_error_2;
|
|
}
|
|
ltwws = 0;
|
|
continue;
|
|
}
|
|
if (ttMWS(ct->type)) {
|
|
if (ltwws == 1) {
|
|
if (ct->type == OPT_NONE) continue;
|
|
ltwws = 2;
|
|
} else if (ltwws == 2) continue;
|
|
else if (ct->type == OPT_NONE) ltwws = 1;
|
|
else ltwws = 2;
|
|
} else ltwws = 0;
|
|
if (ct->line >= 0) ct->line = l;
|
|
print_token(ls, ct, reject_nested ? 0 : l);
|
|
}
|
|
if (etl.nt) freemem(etl.t);
|
|
if (tfi) {
|
|
tfi->art = save_tfi + (etl.art - etl_limit);
|
|
}
|
|
|
|
exit_macro_1:
|
|
print_space(ls);
|
|
exit_macro_2:
|
|
for (i = 0; i < (m->narg + m->vaarg); i ++)
|
|
if (atl[i].nt) freemem(atl[i].t);
|
|
if (m->narg > 0 || m->vaarg) freemem(atl);
|
|
m->nest = save_nest;
|
|
return 0;
|
|
|
|
exit_error_2:
|
|
if (etl.nt) freemem(etl.t);
|
|
exit_error_1:
|
|
for (i = 0; i < (m->narg + m->vaarg); i ++)
|
|
if (atl[i].nt) freemem(atl[i].t);
|
|
if (m->narg > 0 || m->vaarg) freemem(atl);
|
|
m->nest = save_nest;
|
|
exit_error:
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* print already defined macros
|
|
*/
|
|
void print_defines(void)
|
|
{
|
|
HTT_scan(¯os, print_macro);
|
|
}
|
|
|
|
/*
|
|
* define_macro() defines a new macro, whom definition is given in
|
|
* the command-line syntax: macro=def
|
|
* The '=def' part is optional.
|
|
*
|
|
* It returns non-zero on error.
|
|
*/
|
|
int define_macro(struct lexer_state *ls, char *def)
|
|
{
|
|
char *c = sdup(def), *d;
|
|
int with_def = 0;
|
|
int ret = 0;
|
|
|
|
for (d = c; *d && *d != '='; d ++);
|
|
if (*d) {
|
|
*d = ' ';
|
|
with_def = 1;
|
|
}
|
|
if (with_def) {
|
|
struct lexer_state lls;
|
|
size_t n = strlen(c) + 1;
|
|
|
|
if (c == d) {
|
|
error(-1, "void macro name");
|
|
ret = 1;
|
|
} else {
|
|
*(c + n - 1) = '\n';
|
|
init_buf_lexer_state(&lls, 0);
|
|
lls.flags = ls->flags | LEXER;
|
|
lls.input = 0;
|
|
lls.input_string = (unsigned char *)c;
|
|
lls.pbuf = 0;
|
|
lls.ebuf = n;
|
|
lls.line = -1;
|
|
ret = handle_define(&lls);
|
|
free_lexer_state(&lls);
|
|
}
|
|
} else {
|
|
struct macro *m;
|
|
|
|
if (!*c) {
|
|
error(-1, "void macro name");
|
|
ret = 1;
|
|
} else if ((m = HTT_get(¯os, c))
|
|
#ifdef LOW_MEM
|
|
&& (m->cval.length != 3
|
|
|| m->cval.t[0] != NUMBER
|
|
|| strcmp((char *)(m->cval.t + 1), "1"))) {
|
|
#else
|
|
&& (m->val.nt != 1
|
|
|| m->val.t[0].type != NUMBER
|
|
|| strcmp(m->val.t[0].name, "1"))) {
|
|
#endif
|
|
error(-1, "macro %s already defined", c);
|
|
ret = 1;
|
|
} else {
|
|
#ifndef LOW_MEM
|
|
struct token t;
|
|
#endif
|
|
|
|
m = new_macro();
|
|
#ifdef LOW_MEM
|
|
m->cval.length = 3;
|
|
m->cval.t = getmem(3);
|
|
m->cval.t[0] = NUMBER;
|
|
m->cval.t[1] = '1';
|
|
m->cval.t[2] = 0;
|
|
#else
|
|
t.type = NUMBER;
|
|
t.name = sdup("1");
|
|
aol(m->val.t, m->val.nt, t, TOKEN_LIST_MEMG);
|
|
#endif
|
|
HTT_put(¯os, m, c);
|
|
}
|
|
}
|
|
freemem(c);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* undef_macro() undefines the macro whom name is given as "def";
|
|
* it is not an error to try to undef a macro that does not exist.
|
|
*
|
|
* It returns non-zero on error (undefinition of a special macro,
|
|
* void macro name).
|
|
*/
|
|
int undef_macro(struct lexer_state *ls, char *def)
|
|
{
|
|
char *c = def;
|
|
|
|
if (!*c) {
|
|
error(-1, "void macro name");
|
|
return 1;
|
|
}
|
|
if (HTT_get(¯os, c)) {
|
|
if (check_special_macro(c)) {
|
|
error(-1, "trying to undef special macro %s", c);
|
|
return 1;
|
|
} else HTT_del(¯os, c);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* We saw a #ifdef directive. Parse the line.
|
|
* return value: 1 if the macro is defined, 0 if it is not, -1 on error
|
|
*/
|
|
int handle_ifdef(struct lexer_state *ls)
|
|
{
|
|
while (!next_token(ls)) {
|
|
int tgd = 1;
|
|
|
|
if (ls->ctok->type == NEWLINE) break;
|
|
if (ttMWS(ls->ctok->type)) continue;
|
|
if (ls->ctok->type == NAME) {
|
|
int x = (HTT_get(¯os, ls->ctok->name) != 0);
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE)
|
|
if (tgd && !ttWHI(ls->ctok->type)
|
|
&& (ls->flags & WARN_STANDARD)) {
|
|
warning(ls->line, "trailing garbage "
|
|
"in #ifdef");
|
|
tgd = 0;
|
|
}
|
|
return x;
|
|
}
|
|
error(ls->line, "illegal macro name for #ifdef");
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE)
|
|
if (tgd && !ttWHI(ls->ctok->type)
|
|
&& (ls->flags & WARN_STANDARD)) {
|
|
warning(ls->line, "trailing garbage in "
|
|
"#ifdef");
|
|
tgd = 0;
|
|
}
|
|
return -1;
|
|
}
|
|
error(ls->line, "unfinished #ifdef");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* for #undef
|
|
* return value: 1 on error, 0 on success. Undefining a macro that was
|
|
* already not defined is not an error.
|
|
*/
|
|
int handle_undef(struct lexer_state *ls)
|
|
{
|
|
while (!next_token(ls)) {
|
|
if (ls->ctok->type == NEWLINE) break;
|
|
if (ttMWS(ls->ctok->type)) continue;
|
|
if (ls->ctok->type == NAME) {
|
|
struct macro *m = HTT_get(¯os, ls->ctok->name);
|
|
int tgd = 1;
|
|
|
|
if (m != 0) {
|
|
if (check_special_macro(ls->ctok->name)) {
|
|
error(ls->line, "trying to undef "
|
|
"special macro %s",
|
|
ls->ctok->name);
|
|
goto undef_error;
|
|
}
|
|
if (emit_defines)
|
|
fprintf(emit_output, "#undef %s\n",
|
|
ls->ctok->name);
|
|
HTT_del(¯os, ls->ctok->name);
|
|
}
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE)
|
|
if (tgd && !ttWHI(ls->ctok->type)
|
|
&& (ls->flags & WARN_STANDARD)) {
|
|
warning(ls->line, "trailing garbage "
|
|
"in #undef");
|
|
tgd = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
error(ls->line, "illegal macro name for #undef");
|
|
undef_error:
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE);
|
|
return 1;
|
|
}
|
|
error(ls->line, "unfinished #undef");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* for #ifndef
|
|
* return value: 0 if the macro is defined, 1 if it is not, -1 on error.
|
|
*/
|
|
int handle_ifndef(struct lexer_state *ls)
|
|
{
|
|
while (!next_token(ls)) {
|
|
int tgd = 1;
|
|
|
|
if (ls->ctok->type == NEWLINE) break;
|
|
if (ttMWS(ls->ctok->type)) continue;
|
|
if (ls->ctok->type == NAME) {
|
|
int x = (HTT_get(¯os, ls->ctok->name) == 0);
|
|
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE)
|
|
if (tgd && !ttWHI(ls->ctok->type)
|
|
&& (ls->flags & WARN_STANDARD)) {
|
|
warning(ls->line, "trailing garbage "
|
|
"in #ifndef");
|
|
tgd = 0;
|
|
}
|
|
if (protect_detect.state == 1) {
|
|
protect_detect.state = 2;
|
|
protect_detect.macro = sdup(ls->ctok->name);
|
|
}
|
|
return x;
|
|
}
|
|
error(ls->line, "illegal macro name for #ifndef");
|
|
while (!next_token(ls) && ls->ctok->type != NEWLINE)
|
|
if (tgd && !ttWHI(ls->ctok->type)
|
|
&& (ls->flags & WARN_STANDARD)) {
|
|
warning(ls->line, "trailing garbage in "
|
|
"#ifndef");
|
|
tgd = 0;
|
|
}
|
|
return -1;
|
|
}
|
|
error(ls->line, "unfinished #ifndef");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* erase the macro table.
|
|
*/
|
|
void wipe_macros(void)
|
|
{
|
|
if (macros_init_done) HTT_kill(¯os);
|
|
macros_init_done = 0;
|
|
}
|
|
|
|
/*
|
|
* initialize the macro table
|
|
*/
|
|
void init_macros(void)
|
|
{
|
|
wipe_macros();
|
|
HTT_init(¯os, del_macro);
|
|
macros_init_done = 1;
|
|
if (!no_special_macros) add_special_macros();
|
|
}
|
|
|
|
/*
|
|
* find a macro from its name
|
|
*/
|
|
struct macro *get_macro(char *name)
|
|
{
|
|
return HTT_get(¯os, name);
|
|
}
|