%{
/* This file is part of Mailfromd.
Copyright (C) 2005, 2006, 2007, 2008 Sergey Poznyakoff
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, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see . */
#define MF_SOURCE_NAME MF_SOURCE_LEX
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include
#include "mailfromd.h"
#include "gram.h"
#include "prog.h"
static struct locus locus; /* Current input location */
static struct locus start_locus; /* Location when the last state switch
occurred */
static struct obstack string_stk; /* Obstack for constructing string values */
static char *multiline_delimiter; /* End of here-document delimiter */
static size_t multiline_delimiter_len; /* Length of multiline_delimiter_len */
static int multiline_unescape; /* Unescape here-document contents */
static int (*char_to_strip)(char); /* Strip matching characters of each
here-document line */
static int
is_tab(char c)
{
return c == '\t';
}
static int
is_space(char c)
{
return c == '\t' || c == ' ';
}
#define line_begin string_begin
#define line_add string_add
#define line_add_char string_add_char
static void line_finish(void);
static void string(const char *str, size_t len);
static int isemptystr(char *text);
static int builtin_const(const char *s);
/* External interface to locus */
const struct locus *
get_locus()
{
return &locus;
}
/* Auxiliary function for returning keyword tokens */
static int
keyword(int kw)
{
yylval.locus = locus;
return kw;
}
/* Input stack support */
#undef YY_INPUT
#define YY_INPUT(buf,result,max_size) do { \
if (ext_pp) \
result = fread(buf, 1, max_size, yyin); \
else \
result = pp_fill_buffer(buf, max_size); \
} while (0)
static int
variable_or_const()
{
union {
struct variable *vptr;
struct constant *cptr;
} v;
const struct value *value_ptr;
switch (variable_or_constant_lookup(yylval.literal->text, (void**)&v)) {
case SYM_UNDEF:
parse_error(_("Variable %s is not defined"),
yylval.literal->text);
return BOGUS;
case SYM_VARIABLE:
yylval.var = v.vptr;
return VARIABLE;
case SYM_CONSTANT:
value_ptr = &v.cptr->value;
switch (value_ptr->type) {
case dtype_number:
yylval.number = value_ptr->v.number;
return NUMBER;
case dtype_string:
yylval.literal = value_ptr->v.literal;
return STRING;
default:
abort();
}
}
}
static size_t input_line;
static void
advance_line()
{
++locus.line;
++input_line;
}
static int
is_stdin(const char *name)
{
return name == NULL
|| strcmp(name, "stdin") == 0
|| strcmp(name, "") == 0
|| strcmp(name, "-") == 0;
}
#define BEGIN_X(s) do { BEGIN(s); start_locus = locus; } while(0)
%}
/* Exclusive states:
COMMENT Within a C-style comment;
STR Processing a complex string;
ML Within a multi-line aggregator. The line being built
requires stripping leading whitespace (if requested).
CML Continuation within a multi-line aggregator. The line
being built does not require stripping leading whitespace.
QML Quoted multi-line aggregator. No variable substitution and
unquoting is needed.
Inclusive states:
ONBLOCK Lexical tie-in after an `on' keyword. In ONBLOCK state
the strigns `as', `host', `from', and `poll' are
recognized as keywords. The string `for' also acquires
special meaning.
*/
%x COMMENT STR ML CML QML
%s ONBLOCK
N [0-9][0-9]*
P [1-9][0-9]*
X [0-9a-fA-F]
O [0-7]
WS [ \t][ \t]*
IDENT [a-zA-Z_][a-zA-Z_0-9]*
LOCUS __file__|__line__|__function__
VCONST __package__|__version__|__major__|__minor__|__patch__
STATEDIR __(def)?statedir__
PREPROC __(def)?preproc__
ICONST {LOCUS}|{VCONST}|{STATEDIR}|{PREPROC}
%%
/* C-style comments */
"/*" BEGIN_X(COMMENT);
[^*\n]* /* eat anything that's not a '*' */
"*"+[^*/\n]* /* eat up '*'s not followed by '/'s */
\n advance_line();
"*"+"/" BEGIN(INITIAL);
/* Configuration directives */
^[ \t]*#[ \t]*{P}[ \t]+\".*\".*\n parse_line_cpp(yytext, &locus);
/* Normally, everything within a comment should be ignored, so
the exclusive condition for the rule below is an error.
Unfortunately, GNU m4 (versions up to 1.4.9) outputs line
synchronisation directives in comments, which makes any decent
compiler lost trace of which input line it is on. To avoid
this, mfd handles #line directives even within a C-comment
block.
The bug report with a patch was sent to bug-m4@gnu.org on
2007-05-24:
http://lists.gnu.org/archive/html/bug-m4/2007-05/msg00018.html
Hopefully it will be fixed in the next release.
*/
^[ \t]*#[ \t]*line[ \t].*\n {
struct locus newloc;
memset(&newloc, 0, sizeof newloc);
parse_line(yytext, &newloc);
if (is_stdin(newloc.file)) {
if (newloc.line > input_line)
locus.line += newloc.line - input_line;
else {
size_t diff = input_line - newloc.line;
if (diff < locus.line)
locus.line -= diff;
}
input_line = newloc.line;
} else {
locus = newloc;
input_line++;
}
}
^[ \t]*#[ \t]*pragma[ \t].*/\n parse_pragma(yytext);
^[ \t]*#[ \t]*error[ \t].*\n { parse_error("%s", yytext); advance_line(); }
/* End-of-line comments */
#.*\n { advance_line(); }
#.* /* end-of-file comment */;
/* Reserved words */
accept return keyword(ACT_ACCEPT);
reject return keyword(ACT_REJECT);
tempfail return keyword(ACT_TEMPFAIL);
continue return keyword(ACT_CONTINUE);
discard return keyword(ACT_DISCARD);
add return keyword(ADD);
replace return keyword(REPLACE);
delete return keyword(DELETE);
if return keyword(IF);
fi return keyword(FI);
else return keyword(ELSE);
elif return keyword(ELIF);
on return keyword(ON);
do return keyword(DO);
done return keyword(DONE);
matches return keyword(MATCHES);
fnmatches return keyword(FNMATCHES);
mx{WS}matches return keyword(MXMATCHES);
mx{WS}fnmatches return keyword(MXFNMATCHES);
when return keyword(WHEN);
or return keyword(OR);
and return keyword(AND);
not return keyword(NOT);
next return keyword(NEXT);
prog return keyword(PROG);
set return keyword(SET);
catch return keyword(CATCH);
echo return keyword(KW_ECHO);
return return keyword(RETURN);
returns return keyword(RETURNS);
func return keyword(FUNC);
switch return keyword(SWITCH);
case return keyword(CASE);
default return keyword(DEFAULT);
string { yylval.type = dtype_string; return TYPE; }
number { yylval.type = dtype_number; return TYPE; }
const return keyword(CONST);
throw return keyword(THROW);
loop return keyword(LOOP);
while return keyword(WHILE);
for return keyword(FOR);
break return keyword(BREAK);
pass return keyword(PASS);
begin return keyword(KW_BEGIN);
end return keyword(KW_END);
alias return keyword(ALIAS);
vaptr return keyword(VAPTR);
{ICONST} { return builtin_const(yytext); }
poll return keyword(POLL);
host return keyword(HOST);
as return keyword(AS);
from return keyword(FROM);
/* Variables */
\%({ICONST}) { return builtin_const(yytext+1); }
\%{IDENT} {
string(yytext + 1, yyleng - 1);
return variable_or_const();
}
\%\{{IDENT}\} {
string(yytext + 2, yyleng - 3);
return variable_or_const();
}
/* Positional arguments */
\$# {
return ARGCOUNT;
}
\${P} {
yylval.number = strtol(yytext + 1, NULL, 0);
return ARG; }
\$/"(" { return keyword(ARGX); }
/* Sendmail variables */
\${IDENT} {
if (yyleng == 2)
string(yytext + 1, 1);
else {
line_begin();
line_add("{", 1);
line_add(yytext + 1, yyleng - 1);
line_add("}", 1);
line_finish();
}
return SYMBOL;
}
\$\{{IDENT}\} {
string(yytext+1, yyleng - 1); return SYMBOL; }
/* Back-references */
\\{P} {
yylval.number = strtoul(yytext+1, NULL, 0);
return BACKREF; }
/* Numeric strings */
{N}\.{N}\.{N} { string(yytext, yyleng); return XCODE; }
[0-9]{3} { string(yytext, yyleng); return CODE; }
0[xX]{X}{X}* { yylval.number = strtoul(yytext, NULL, 16); return NUMBER; };
0{O}{O}* { yylval.number = strtoul(yytext, NULL, 8); return NUMBER; };
0|{P} { yylval.number = strtoul(yytext, NULL, 10); return NUMBER; };
/* Strings */
{IDENT} {
int paren_follows = 0;
enum { is_ident, is_builtin, is_func } state;
if (yylval.builtin = builtin_lookup(yytext))
state = is_builtin;
else if (yylval.function = function_lookup(yytext))
state = is_func;
else
state = is_ident;
if (state != is_ident) {
int c;
while ((c = input()) && c_isascii(c) && c_isspace(c))
if (c == '\n')
advance_line();
paren_follows = (c == '(');
unput(c);
}
if (state == is_builtin)
return yylval.builtin->rettype == dtype_unspecified ?
BUILTIN_PROC :
(paren_follows ? BUILTIN : BUILTIN_P);
else if (state == is_func)
return yylval.function->rettype == dtype_unspecified ?
FUNCTION_PROC :
(paren_follows ? FUNCTION : FUNCTION_P);
else {
string(yytext, yyleng);
return IDENTIFIER;
}
}
'[^\n']*' { string(yytext+1, yyleng-2); return STRING; }
\"[^\\\"$%\n]*\" { string(yytext+1, yyleng-2); return STRING; }
\"[^\\\"$%\n]*\\\n { advance_line();
BEGIN_X(STR);
line_begin();
line_add(yytext + 1, yyleng - 3); }
\"[^\\\"$%\n]*/[\\$%] { BEGIN_X(STR);
string(yytext+1, yyleng-1);
line_begin();
return STRING; }
\"\\x{X}{X}/[\\$%] {
BEGIN_X(STR);
line_add_char(strtoul(yytext + 3, NULL, 16));
line_finish();
return STRING;
}
\"\\x{X}{X} {
BEGIN_X(STR);
line_add_char(strtoul(yytext + 3, NULL, 16));
}
\"\\0{O}{1,3}/[\\$%] {
BEGIN_X(STR);
line_add_char(strtoul(yytext + 3, NULL, 8));
line_finish();
return STRING;
}
\"\\0{O}{1,3} {
BEGIN_X(STR);
line_add_char(strtoul(yytext + 3, NULL, 8));
}
\"\\[^1-9]/[\\$%] {
BEGIN_X(STR);
line_add_char(mu_argcv_unquote_char(yytext[2]));
line_finish();
return STRING;
}
\"\\[^1-9] {
BEGIN_X(STR);
line_add_char(mu_argcv_unquote_char(yytext[2]));
}
[^\\\"$%\n]*\\\n { advance_line(); line_add(yytext, yyleng - 2); }
[^\\\"$%\n]*\" { BEGIN(INITIAL);
if (yyleng > 1)
line_add(yytext, yyleng - 1);
line_finish();
return STRING; }
[^\\\"$%\n]+/[\\$%] {
line_add(yytext, yyleng);
line_finish();
line_begin();
return STRING;
}
\\x{X}{X}/[\\$%] {
line_add_char(strtoul(yytext + 2, NULL, 16));
line_finish();
line_begin();
return STRING;
}
\\x{X}{X} {
line_add_char(strtoul(yytext + 2, NULL, 16));
}
\\0{O}{1,3}/[\\$%] {
line_add_char(strtoul(yytext + 2, NULL, 8));
line_finish();
line_begin();
return STRING;
}
\\0{O}{1,3} {
line_add_char(strtoul(yytext + 2, NULL, 8));
}
\\[^1-9]/[\\$%] {
line_add_char(mu_argcv_unquote_char(yytext[1]));
line_finish();
line_begin();
return STRING;
}
\\[^1-9] {
line_add_char(mu_argcv_unquote_char(yytext[1]));
}
/* Multi-line strings */
"<<"(-" "?)?\\?{IDENT}[ \t]*.*\n |
"<<"(-" "?)?'{IDENT}'[ \t]*.*\n {
char *p;
advance_line();
char_to_strip = NULL;
multiline_unescape = 1;
line_begin();
p = yytext + 2;
if (*p == '-') {
++p;
if (*p == ' ') {
++p;
char_to_strip = is_space;
} else
char_to_strip = is_tab;
}
if (*p == '\\') {
p++;
multiline_unescape = 0;
}
if (*p == '\'') {
char *q;
p++;
multiline_unescape = 0;
q = strchr(p, '\'');
multiline_delimiter_len = q - p;
} else
multiline_delimiter_len = strcspn(p, " \t");
multiline_delimiter = xmalloc(multiline_delimiter_len + 1);
memcpy(multiline_delimiter, p, multiline_delimiter_len);
multiline_delimiter[multiline_delimiter_len] = 0;
if (multiline_unescape)
BEGIN_X(ML);
else
BEGIN_X(QML);
}
/* Quoted multilines */
[^\\\n]*\n {
char *p;
advance_line();
p = yytext;
if (char_to_strip)
for (; char_to_strip (*p); p++)
;
if (strlen(p) >= multiline_delimiter_len
&& memcmp(p, multiline_delimiter, multiline_delimiter_len) == 0
&& isemptystr(p + multiline_delimiter_len)) {
free (multiline_delimiter);
multiline_delimiter = NULL;
multiline_delimiter_len = 0;
BEGIN(INITIAL);
line_finish();
return STRING;
}
line_add(p, strlen(p));
}
/* Unquoted multilines */
[^\\$%\n]+/[\\$%] {
char *p = yytext;
if (char_to_strip)
for (; char_to_strip (*p); p++)
;
line_add(p, strlen(p));
line_finish();
BEGIN(CML);
return STRING;
}
[^\\$%\n]+/[\\$%] {
line_add(yytext, yyleng);
line_finish();
line_begin();
return STRING;
}
"%%"|"$$" {
line_add(yytext, 1);
line_finish();
return STRING;
}
[$%] {
line_add(yytext, yyleng);
}
[^\\$%\n]*\n {
char *p;
advance_line();
p = yytext;
if (char_to_strip)
for (; char_to_strip (*p); p++)
;
if (strlen(p) >= multiline_delimiter_len
&& memcmp(p, multiline_delimiter, multiline_delimiter_len) == 0
&& isemptystr(p + multiline_delimiter_len)) {
free (multiline_delimiter);
multiline_delimiter = NULL;
multiline_delimiter_len = 0;
BEGIN(INITIAL);
line_finish();
return STRING;
}
line_add(p, strlen(p));
}
[^\\$%\n]*\n {
advance_line();
if (yyleng >= multiline_delimiter_len
&& memcmp(yytext, multiline_delimiter,
multiline_delimiter_len) == 0
&& isemptystr(yytext + multiline_delimiter_len)) {
free (multiline_delimiter);
multiline_delimiter = NULL;
multiline_delimiter_len = 0;
BEGIN(INITIAL);
line_finish();
return STRING;
}
line_add(yytext, yyleng);
BEGIN_X(ML);
}
/* Other tokens */
{WS} ;
\n { advance_line(); }
"="|"==" return keyword(EQ);
"!=" return keyword(NE);
"<" return keyword(LT);
"<=" return keyword(LE);
">" return keyword(GT);
">=" return keyword(GE);
"&" return keyword(LOGAND);
"|" return keyword(LOGOR);
"^" return keyword(LOGXOR);
"~" return keyword(LOGNOT);
"..." return keyword(DOTS);
. /* If a here-document is not closed and its next line does not
end with a \n, prevent it from being displayed by ECHO */;
. return yytext[0];
%%
void
init_string_space()
{
obstack_init(&string_stk);
}
void
free_string_space()
{
obstack_free(&string_stk, NULL);
}
char *
mf_strdup(const char *str)
{
string_add(str, strlen(str) + 1);
return obstack_finish(&string_stk);
}
struct literal *
string_alloc(const char *str, size_t len)
{
string_begin();
string_add(str, len);
return string_finish();
}
static void
string(const char *str, size_t len)
{
yylval.literal = string_alloc(str, len);
}
void
string_begin()
{
/* nothing */
}
struct literal *
string_finish()
{
char *ptr;
struct literal *lit;
obstack_1grow(&string_stk, 0);
ptr = obstack_finish(&string_stk);
lit = literal_lookup(ptr);
if (lit->text != ptr)
obstack_free(&string_stk, ptr);
return lit;
}
static void
line_finish()
{
yylval.literal = string_finish();
if (yy_flex_debug)
fprintf(stderr, "constructed line: %s\n",
yylval.literal->text);
}
void
string_add(const char *str, size_t len)
{
obstack_grow(&string_stk, str, len);
}
void
string_add_char(unsigned char c)
{
obstack_1grow(&string_stk, c);
}
static void
print_parse_message(const struct locus *locus,
const char *prefix, const char *fmt, va_list ap)
{
int n;
char buf[512]; /* FIXME */
if (locus && locus->file)
n = snprintf(buf, sizeof buf, "%s:%lu: ",
locus->file, (unsigned long) locus->line);
else
n = 0;
if (prefix)
n += snprintf(buf + n, sizeof buf - n, "%s: ", prefix);
vsnprintf(buf + n, sizeof buf - n, gettext(fmt), ap);
mu_error(buf);
}
void
parse_warning(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print_parse_message(&locus, _("Warning"), fmt, ap);
va_end(ap);
}
void
parse_warning_locus(const struct locus *loc, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print_parse_message(loc, _("Warning"), fmt, ap);
va_end(ap);
}
void
parse_error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print_parse_message(&locus, NULL, fmt, ap);
va_end(ap);
error_count++;
}
void
parse_error_locus(const struct locus *loc, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
print_parse_message(loc, NULL, fmt, ap);
va_end(ap);
error_count++;
}
static pid_t pp_pid;
int
source(char *name)
{
if (ext_pp) {
int fd;
char *ppcmd;
int argc;
char **argv;
fd = open(name, O_RDONLY);
if (fd == -1) {
mu_error(_("Cannot open `%s': %s"),
name, mu_strerror(errno));
return EX_NOINPUT;
}
close(fd);
pp_make_argcv(&argc, &argv);
yyin = pp_extrn_start(argc, argv, &pp_pid);
if (!yyin) {
mu_error(_("Unable to start external preprocessor `%s': %s"),
ppcmd,
mu_strerror(errno));
return EX_OSFILE;
}
} else
return pp_init(name) ? EX_NOINPUT : EX_OK;
return EX_OK;
}
int
yywrap()
{
if (yyin)
pp_extrn_shutdown(pp_pid);
else
pp_done();
switch (YYSTATE) {
case INITIAL:
/* ok */
break;
case COMMENT:
parse_error_locus(&start_locus, _("End of file in comment"));
break;
case STR:
case ML:
case CML:
case QML:
parse_error_locus(&start_locus, _("End of file in string"));
}
locus.file = NULL;
return 1;
}
static int
isemptystr(char *text)
{
for (; *text && c_isspace (*text); text++)
;
return *text == 0;
}
void
onblock(int enable)
{
if (enable)
BEGIN(ONBLOCK);
else
BEGIN(INITIAL);
}
static int
builtin_const(const char *s)
{
if (strcmp(s, "__file__") == 0) {
yylval.literal = literal_lookup(locus.file);
return STRING;
} else if (strcmp(s, "__line__") == 0) {
yylval.number = locus.line;
return NUMBER;
} else if (strcmp(s, "__function__") == 0) {
s = function_name();
string(s, strlen(s));
return STRING;
} else if (strcmp(s, "__package__") == 0) {
string(PACKAGE_TARNAME, sizeof PACKAGE_TARNAME - 1);
return STRING;
} else if (strcmp(s, "__version__") == 0) {
string(PACKAGE_VERSION, sizeof PACKAGE_VERSION - 1);
return STRING;
} else if (strcmp(s, "__major__") == 0) {
yylval.number = MAILFROMD_VERSION_MAJOR;
return NUMBER;
} else if (strcmp(s, "__minor__") == 0) {
yylval.number = MAILFROMD_VERSION_MINOR;
return NUMBER;
} else if (strcmp(s, "__patch__") == 0) {
yylval.number = MAILFROMD_VERSION_PATCH;
return NUMBER;
} else if (strcmp(s, "__statedir__") == 0) {
string(mailfromd_state_dir, strlen(mailfromd_state_dir));
return STRING;
} else if (strcmp(s, "__defstatedir__") == 0) {
string(DEFAULT_STATE_DIR, sizeof DEFAULT_STATE_DIR - 1);
return STRING;
} else if (strcmp(s, "__preproc__") == 0) {
if (!ext_pp)
string("", 0);
else
string(ext_pp, strlen(ext_pp));
return STRING;
} else if (strcmp(s, "__defpreproc__") == 0) {
if (DEFAULT_PREPROCESSOR)
string(DEFAULT_PREPROCESSOR,
sizeof DEFAULT_PREPROCESSOR - 1);
else
string("", 0);
return STRING;
}
abort();
}
/* End of lex.l */