%{
/* 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_GRAM
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include "mailfromd.h"
#include "prog.h"
#include "optab.h"
#define obstack_chunk_alloc malloc
#define obstack_chunk_free free
#include "obstack.h"
static NODE *alloc_node(enum node_type type, const struct locus *locus);
static void free_node(NODE *node);
static void set_poll_arg(struct poll_data *poll, int kw, NODE *expr);
static int codegen(prog_counter_t *pc, NODE *node, int finalize,
size_t nautos);
static void mark(NODE *node);
static void dataseg_layout(void);
static void regex_layout(void);
static int optimize_tree(NODE *node);
static void compile_tree(NODE *node);
static NODE *reverse(NODE *in);
static NODE *function_call(struct function *function, size_t count,
NODE *subtree);
static NODE *declare_function(struct function *func, struct locus *loc,
size_t nautos);
static data_type_t node_type(NODE *node);
static NODE *cast_to(data_type_t type, NODE *node);
static NODE *cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype);
static void add_xref(struct variable *var, const struct locus *locus);
static struct variable *vardecl(const char *name, data_type_t type,
storage_class_t sc, struct locus *loc);
static struct variable *externdecl(const char *name, struct value *value,
struct locus *loc);
static int initialize_variable(struct variable *var, struct value *val,
const struct locus *locus);
static void apply_deferred_init(void);
static NODE *create_asgn_node(struct variable *var, NODE *expr,
const struct locus *loc);
NODE *defined_parm(struct variable *var, struct locus *locus);
static void register_auto(struct variable *var);
static void unregister_auto(struct variable *var);
static size_t forget_autos(size_t nparam, size_t prev, size_t hidden_arg);
static void optimize(NODE *node);
static NODE *root_node[smtp_state_count];
prog_counter_t entry_point[smtp_state_count];
#define PS_BEGIN 0
#define PS_END 1
int regex_flags; /* Should default to REG_NOSUB ? */
unsigned error_count; /* Number of detected errors */
size_t variable_count = 1; /* Offset of the next-to-be-defined variable
in the data segment.
Offset 0 is reserved for NULL symbol */
static enum smtp_state state_tag; /* State tag of the currently processed
PROG */
static struct function *func; /* The currently compiled function */
static prog_counter_t jump_pc; /* Pointer to the chain of jmp instructions */
/* Outermost context decides how to code exits from catches.
Innermost context is used to access parameters. */
enum lexical_context outer_context, inner_context;
size_t catch_nesting; /* Nesting level for catch statements */
/* Controls whether unquoted strings are warned about. Normally it is off
within a `when .*:' construct. */
static int allow_unquoted_strings;
static struct stmtlist genstmt; /* List of generated statements */
/* State handlers and their positional parameters */
struct state_parms {
int cnt; /* Number or positional parameters */
data_type_t types[4]; /* Their data types */
} state_parms[] = {
{ 0, }, /* smtp_state_none */
{ 0, }, /* smtp_state_begin */
{ 4, { dtype_string, dtype_number, dtype_number, dtype_string } },
/* smtp_state_connect */
{ 1, { dtype_string } }, /* smtp_state_helo */
{ 1, { dtype_string, dtype_string } }, /* smtp_state_envfrom */
{ 1, { dtype_string, dtype_string } }, /* smtp_state_envrcpt */
{ 0, }, /* smtp_state_data */
{ 2, { dtype_string, dtype_string } }, /* smtp_state_header */
{ 0, }, /* smtp_state_eoh */
{ 2, { dtype_string, dtype_number } }, /* smtp_state_body */
{ 0, }, /* smtp_state_eom */
{ 0, } /* smtp_state_end */
};
struct parmtype {
struct parmtype *next;
data_type_t type;
};
static int
parmcount_none()
{
return 0;
}
static data_type_t
parmtype_none(int n)
{
return dtype_unspecified;
}
static int
parmcount_handler()
{
return state_parms[state_tag].cnt;
}
static data_type_t
parmtype_handler(int n)
{
return state_parms[state_tag].types[n-1];
}
static int
parmcount_catch()
{
return 2;
}
static data_type_t
parmtype_catch(int n)
{
switch (n) {
case 1:
return dtype_number;
case 2:
return dtype_string;
}
abort();
}
static int
parmcount_function()
{
return func->parmcount;
}
static data_type_t
parmtype_function(int n)
{
return func->parmtype[n-1];
}
struct parminfo {
int (*parmcount)(void);
data_type_t (*parmtype)(int n);
} parminfo[] = {
{ parmcount_none, parmtype_none },
{ parmcount_handler, parmtype_handler },
{ parmcount_catch, parmtype_catch },
{ parmcount_function, parmtype_function },
};
#define PARMCOUNT() parminfo[inner_context].parmcount()
#define PARMTYPE(n) parminfo[inner_context].parmtype(n)
#define FUNC_HIDDEN_ARGS(f) ((f)->optcount ? 1 : 0)
/* Loop stack */
data_type_t
string_to_type(const char *s)
{
if (strcmp(s, "n") == 0)
return dtype_number;
else if (strcmp(s, "s") == 0)
return dtype_string;
else
return dtype_unspecified;
}
const char *
type_to_string(data_type_t t)
{
switch (t) {
case dtype_number:
return "number";
case dtype_string:
return "string";
case dtype_unspecified:
return "unspecified";
default:
abort();
}
}
static int
check_func_usage(struct function *fp)
{
switch (outer_context) {
case context_handler:
if (fp->statemask && !(EXMASK(state_tag) & fp->statemask)) {
parse_error(_("Function `%s' cannot be used in "
"prog `%s'"),
fp->name,
state_to_string(state_tag));
return 1;
}
break;
case context_function:
func->statemask |= fp->statemask;
break;
default:
break;
}
return 0;
}
static int
check_builtin_usage(const struct builtin *bp)
{
switch (outer_context) {
case context_handler:
if (bp->statemask && !(EXMASK(state_tag) & bp->statemask)) {
parse_error(_("Built-in function `%s' cannot be used in "
"prog `%s'"),
bp->name,
state_to_string(state_tag));
return 1;
}
break;
case context_function:
func->statemask |= bp->statemask;
break;
default:
break;
}
if (bp->capture)
capture_on();
return 0;
}
static void jump_fixup(prog_counter_t pos, prog_counter_t endpos);
static NODE *create_on_node(NODE *sel, struct case_stmt *cases);
static void register_macro(enum smtp_state tag, const char *macro);
#define LITERAL_TEXT(lit) ((lit) ? (lit)->text : NULL)
#define LITERAL_OFF(lit) ((lit) ? (lit)->off : 0)
%}
%error-verbose
%expect 26
%union {
struct literal *literal;
struct stmtlist stmtlist;
NODE *node;
struct return_node ret;
struct poll_data poll;
struct locus locus;
struct pollarg {
int kw;
NODE *expr;
} pollarg;
struct arglist {
NODE *head;
NODE *tail;
size_t count;
} arglist;
long number;
const struct builtin *builtin;
struct variable *var;
enum smtp_state state;
struct {
struct locus locus;
int qualifier;
} matchtype;
data_type_t type;
struct parmtype *parm;
struct parmlist {
struct parmtype *head, *tail;
size_t count;
size_t optcount;
} parmlist;
enum lexical_context tie_in;
struct function *function;
struct {
struct valist *head, *tail;
} valist_list;
struct valist *valist;
struct value value;
struct {
struct case_stmt *head, *tail;
} case_list ;
struct case_stmt *case_stmt;
struct loop_node loop;
struct {
struct locus locus;
int code;
} progspecial;
};
%token ACT_ACCEPT ACT_REJECT ACT_TEMPFAIL ACT_CONTINUE ACT_DISCARD
%token ADD REPLACE DELETE
%token PROG KW_BEGIN KW_END IF FI ELSE ELIF
%token ON HOST FROM AS DO DONE POLL MATCHES FNMATCHES
%token MXMATCHES MXFNMATCHES
%token WHEN PASS SET CATCH THROW KW_ECHO RETURNS RETURN FUNC
%token SWITCH CASE DEFAULT CONST
%token FOR LOOP WHILE BREAK NEXT ARGCOUNT
%token STRING CODE XCODE
%token SYMBOL VARIABLE IDENTIFIER
%token ARG NUMBER BACKREF
%token BUILTIN BUILTIN_PROC BUILTIN_P
%token OR AND EQ NE LT LE GT GE NOT LOGAND LOGOR LOGXOR LOGNOT
%token FUNCTION FUNCTION_PROC FUNCTION_P
%token TYPE
%left CONCAT
%left OR
%left AND
%left NOT
%left LOGOR
%left LOGXOR
%left LOGAND
%nonassoc EQ NE MATCHES FNMATCHES MXMATCHES MXFNMATCHES
%nonassoc LT LE GT GE
%left '+' '-'
%left '*' '/'
%left UMINUS
%left BUILTIN FUNCTION FUNCTION_P BUILTIN_P
%type decl stmt condition action sendmail_action header_action
if_cond else_cond on_cond atom
funcall proccall expr common_expr simp_expr atom_expr
asgn catch throw return case_cond autodcl constdecl
loopstmt opt_while jumpstmt
%type stmtlist decllist
%type triplet maybe_triplet
%type pollstmt pollarglist
%type pollarg loop_parm
%type opt_loop_parms loop_parm_list
%type number
%type arglist
%type variable
%type string opt_ident loop_ident
%type state_ident
%type matches fnmatches
%type retdecl
%type parmlist parmdecl
%type parm
%type fundecl
%type value
%type valist catchlist
%type cond_branches branches
%type cond_branch branch
%type on
%type progspecial
%%
input : decllist
{
if (error_count == 0) {
builtin_post_setup();
if (genstmt.head) {
genstmt.tail->next = $1.head;
genstmt.tail = $1.tail;
} else
genstmt = $1;
optimize_tree(genstmt.head);
if (error_count)
YYERROR;
mark(genstmt.head);
if (error_count)
YYERROR;
apply_deferred_init();
dataseg_layout();
regex_layout();
compile_tree(genstmt.head);
}
}
;
decllist : decl
{
$$.head = $$.tail = $1;
}
| decllist decl
{
if ($2) {
if ($1.tail)
$1.tail->next = $2;
else
$1.head = $2;
$1.tail = $2;
}
$$ = $1;
}
;
decl : PROG state_ident DO stmtlist DONE
{
$$ = alloc_node(node_type_progdecl, &$1);
$$->v.progdecl.tag = $2;
$$->v.progdecl.auto_count = forget_autos(PARMCOUNT(),
0, 0);
$$->v.progdecl.tree = $4.head;
outer_context = inner_context = context_none;
state_tag = smtp_state_none;
}
| progspecial DO stmtlist DONE
{
static NODE *progspecial[2];
NODE *np = progspecial[$1.code];
if (!np) {
np = alloc_node(node_type_progdecl, &$1.locus);
$$ = progspecial[$1.code] = np;
np->v.progdecl.tag = state_tag;
np->v.progdecl.tree = $3.head;
np->v.progdecl.auto_count = 0;
} else {
NODE *cur;
for (cur = np->v.progdecl.tree; cur->next;
cur = cur->next)
;
cur->next = $3.head;
$$ = NULL;
}
np->v.progdecl.auto_count = forget_autos(PARMCOUNT(),
np->v.progdecl.auto_count,
0);
outer_context = inner_context = context_none;
state_tag = smtp_state_none;
}
| FUNC fundecl DO stmtlist DONE
{
func->node = $4.head;
$$ = declare_function(func, &$1,
forget_autos(PARMCOUNT() + FUNC_HIDDEN_ARGS(func),
0, FUNC_HIDDEN_ARGS(func)));
outer_context = inner_context = context_none;
func = NULL;
}
| vardecl
{
$$ = NULL;
}
| constdecl
{
$$ = NULL;
}
;
progspecial: KW_BEGIN
{
state_tag = smtp_state_begin;
outer_context = inner_context = context_handler;
$$.locus = $1;
$$.code = PS_BEGIN;
}
| KW_END
{
state_tag = smtp_state_end;
outer_context = inner_context = context_handler;
$$.locus = $1;
$$.code = PS_END;
}
;
vardecl : TYPE IDENTIFIER
{
if (!vardecl($2->text, $1, storage_extern, NULL))
YYERROR;
}
| TYPE IDENTIFIER expr
{
struct value value;
struct variable *var;
var = vardecl($2->text, $1, storage_extern, NULL);
if (!var)
YYERROR;
if (optimization_level)
optimize($3);
value.type = node_type($3);
switch ($3->type) {
case node_type_string:
value.v.literal = $3->v.literal;
break;
case node_type_number:
value.v.number = $3->v.number;
break;
default:
yyerror("initializer element is not constant");
YYERROR;
}
if (initialize_variable(var, &value, &$3->locus))
YYERROR;
}
| SET IDENTIFIER expr
{
struct value value;
if (optimization_level)
optimize($3);
value.type = node_type($3);
switch ($3->type) {
case node_type_string:
value.v.literal = $3->v.literal;
break;
case node_type_number:
value.v.number = $3->v.number;
break;
default:
yyerror("initializer element is not constant");
YYERROR;
}
if (!externdecl($2->text, &value, &$1))
YYERROR;
}
;
constdecl : CONST IDENTIFIER expr
{
struct value value;
if (optimization_level)
optimize($3);
switch ($3->type) {
case node_type_string:
value.type = dtype_string;
value.v.literal = $3->v.literal;
break;
case node_type_number:
value.type = dtype_number;
value.v.number = $3->v.number;
break;
default:
yyerror("initializer element is not constant");
YYERROR;
}
define_constant($2->text, &value, &$1);
$$ = NULL;
}
;
fundecl : IDENTIFIER '(' parmdecl ')' retdecl
{
data_type_t *ptypes = NULL;
if ($3.count) {
int i;
struct parmtype *p;
ptypes = xmalloc($3.count * sizeof *ptypes);
for (i = 0, p = $3.head; p; i++) {
struct parmtype *next = p->next;
ptypes[i] = p->type;
free(p);
p = next;
}
}
$$ = func = function_install($1->text,
$3.count, $3.optcount,
ptypes, $5,
get_locus());
outer_context = inner_context = context_function;
}
;
parmdecl : /* empty */
{
$$.count = $$.optcount = 0;
}
| parmlist
| parmlist ';' parmlist
{
$1.count += $3.count;
$1.optcount = $3.count;
$1.tail->next = $3.head;
$1.tail = $3.tail;
$$ = $1;
}
;
parmlist : parm
{
$$.count = 1;
$$.head = $$.tail = $1;
}
| parmlist ',' parm
{
$1.count++;
$1.tail->next = $3;
$1.tail = $3;
$$ = $1;
}
;
parm : TYPE IDENTIFIER
{
if (!vardecl($2->text, $1, storage_param, NULL))
YYERROR;
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
$$->type = $1;
}
| TYPE
{
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
$$->type = $1;
}
| IDENTIFIER
{
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
if (($$->type = string_to_type($1->text)) == dtype_unspecified)
parse_error(_("Unknown type specification: %s"),
$1->text);
}
;
retdecl : /* empty */
{
$$ = dtype_unspecified;
}
| RETURNS TYPE
{
$$ = $2;
}
| RETURNS IDENTIFIER
{
if (($$ = string_to_type($2->text)) == dtype_unspecified)
parse_error(_("Unknown type specification: %s"),
$2->text);
}
;
state_ident: IDENTIFIER
{
$$ = string_to_state($1->text);
if ($$ == smtp_state_none)
parse_error(_("Unknown smtp state tag: %s"),
$1->text);
state_tag = $$;
outer_context = inner_context = context_handler;
}
;
stmtlist : stmt
{
if ($1)
$1->next = NULL;
$$.head = $$.tail = $1;
}
| stmtlist stmt
{
if ($2) {
if ($1.tail)
$1.tail->next = $2;
else
$1.head = $2;
$1.tail = $2;
}
$$ = $1;
}
;
stmt : condition
| action
| asgn
| autodcl
| catch
| throw
| return
| proccall
| constdecl
| loopstmt
| jumpstmt
;
asgn : SET IDENTIFIER expr
{
struct variable *var;
data_type_t t = node_type($3);
if (t == dtype_unspecified) {
parse_error(_("Unspecified value not ignored as "
"it should be"));
YYERROR;
}
var = variable_lookup($2->text);
if (!var) {
var = vardecl($2->text, t, storage_auto, &$1);
if (!var)
YYERROR;
}
$$ = create_asgn_node(var, $3, &$1);
if (!$$)
YYERROR;
}
;
autodcl : TYPE IDENTIFIER
{
if (!vardecl($2->text, $1, storage_auto, NULL))
YYERROR;
$$ = NULL;
}
| TYPE IDENTIFIER expr
{
struct variable *var = vardecl($2->text, $1,
storage_auto, NULL);
if (!var)
YYERROR;
$$ = create_asgn_node(var, $3, get_locus());
if (!$$)
YYERROR;
}
;
action : sendmail_action
{
if (inner_context == context_handler) {
if (state_tag == smtp_state_begin)
parse_error(_("Sendmail action is not "
"allowed in begin block"));
else if (state_tag == smtp_state_end)
parse_error(_("Sendmail action is not "
"allowed in end block"));
}
}
| header_action
{
if (inner_context == context_handler
&& state_tag == smtp_state_end)
parse_error(_("Header action is not allowed "
"in end block"));
}
| PASS
{
$$ = alloc_node(node_type_noop, &$1);
}
| KW_ECHO expr
{
$$ = alloc_node(node_type_echo, &$1);
$$->v.node = cast_to(dtype_string, $2);
}
;
sendmail_action:
ACT_ACCEPT maybe_triplet
{
if ($2.code || $2.xcode || $2.message)
parse_warning(_("Arguments are ignored for accept"));
$$ = alloc_node(node_type_result, &$1);
$$->v.ret = $2;
$$->v.ret.stat = SMFIS_ACCEPT;
}
| ACT_REJECT maybe_triplet
{
if ($2.code && $2.code->text[0] != '5')
parse_error(_("Reject code should be 5xx"));
if ($2.xcode && $2.xcode->text[0] != '5')
parse_error(_("Reject extended code should be 5.x.x"));
$$ = alloc_node(node_type_result, &$1);
$$->v.ret = $2;
$$->v.ret.stat = SMFIS_REJECT;
}
| ACT_TEMPFAIL maybe_triplet
{
if ($2.code && $2.code->text[0] != '4')
parse_error(_("Tempfail code should be 4xx"));
if ($2.xcode && $2.xcode->text[0] != '4')
parse_error(_("Tempfail extended code should be 4.x.x"));
$$ = alloc_node(node_type_result, &$1);
$$->v.ret = $2;
$$->v.ret.stat = SMFIS_TEMPFAIL;
}
| ACT_CONTINUE
{
$$ = alloc_node(node_type_result, &$1);
memset(&$$->v.ret, 0, sizeof $$->v.ret);
$$->v.ret.stat = SMFIS_CONTINUE;
}
| ACT_DISCARD
{
$$ = alloc_node(node_type_result, &$1);
memset(&$$->v.ret, 0, sizeof $$->v.ret);
$$->v.ret.stat = SMFIS_DISCARD;
}
;
header_action:
ADD string expr
{
$$ = alloc_node(node_type_header, &$1);
$$->v.hdr.opcode = header_add;
$$->v.hdr.name = $2;
$$->v.hdr.value = $3;
}
| REPLACE string expr
{
$$ = alloc_node(node_type_header, &$1);
$$->v.hdr.opcode = header_replace;
$$->v.hdr.name = $2;
$$->v.hdr.value = $3;
}
| DELETE string
{
$$ = alloc_node(node_type_header, &$1);
$$->v.hdr.opcode = header_delete;
$$->v.hdr.name = $2;
$$->v.hdr.value = NULL;
}
;
maybe_triplet: /* empty */
{
memset(&$$, 0, sizeof $$);
}
| triplet
;
triplet : CODE
{
$$.code = $1;
$$.xcode = NULL;
$$.message = NULL;
}
| CODE XCODE
{
$$.code = $1;
$$.xcode = $2;
$$.message = NULL;
}
| CODE XCODE expr
{
$$.code = $1;
$$.xcode = $2;
$$.message = cast_to(dtype_string, $3);
}
| CODE expr
{
$$.code = $1;
$$.xcode = NULL;
$$.message = cast_to(dtype_string, $2);
}
;
condition : if_cond
| case_cond
| on_cond
;
if_cond : IF expr stmtlist else_cond FI
{
$$ = alloc_node(node_type_if, &$1);
$$->v.cond.cond = $2;
$$->v.cond.if_true = $3.head;
$$->v.cond.if_false = $4;
}
;
else_cond : /* empty */
{
$$ = NULL;
}
| ELIF expr stmtlist else_cond
{
$$ = alloc_node(node_type_if, &$1);
$$->v.cond.cond = $2;
$$->v.cond.if_true = $3.head;
$$->v.cond.if_false = $4;
}
| ELSE stmtlist
{
$$ = $2.head;
}
;
case_cond : SWITCH expr DO cond_branches DONE
{
struct case_stmt *defcase = NULL, *pcase, *prev;
$$ = alloc_node(node_type_switch, &$1);
$$->v.switch_stmt.node = $2;
/* Make sure there is only one default case and
place it at the beginning of the list */
pcase = $4.head;
if (!pcase->valist) {
defcase = pcase;
$4.head = $4.head->next;
}
prev = pcase;
pcase = pcase->next;
while (pcase) {
if (!pcase->valist) {
if (defcase) {
parse_error_locus(&pcase->locus,
_("Duplicate default statement"));
parse_error_locus(&defcase->locus,
_("Previously defined here"));
YYERROR;
}
defcase = pcase;
prev->next = pcase->next;
} else
prev = pcase;
pcase = pcase->next;
}
if (!defcase) {
defcase = xmalloc(sizeof *defcase);
defcase->locus = *get_locus();
defcase->valist = NULL;
defcase->node = alloc_node(node_type_noop,
&defcase->locus);
}
defcase->next = $4.head;
$$->v.switch_stmt.cases = defcase;
}
;
cond_branches: cond_branch
{
$$.head = $$.tail = $1;
}
| cond_branches cond_branch
{
$$.tail->next = $2;
$$.tail = $2;
}
;
cond_branch: CASE valist ':' stmtlist
{
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
$$->locus = $1;
$$->valist = $2.head;
$$->node = $4.head;
}
| DEFAULT ':' stmtlist
{
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
$$->locus = $1;
$$->valist = NULL;
$$->node = $3.head;
}
;
valist : value
{
$$.head = $$.tail = xmalloc(sizeof($$.head[0]));
$$.head->next = NULL;
$$.head->value = $1;
}
| valist OR value
{
struct valist *p = xmalloc(sizeof(*p));
p->value = $3;
p->next = NULL;
$1.tail->next = p;
$1.tail = p;
$$ = $1;
}
;
value : STRING
{
$$.type = dtype_string;
$$.v.literal = $1;
}
| IDENTIFIER
{
struct value *value_ptr = constant_lookup($1->text);
if (value_ptr)
$$ = *value_ptr;
else {
if (!allow_unquoted_strings)
parse_warning(_("Unquoted identifier `%s'"),
$1->text);
$$.type = dtype_string;
$$.v.literal = $1;
}
}
| number
{
$$.type = dtype_number;
$$.v.number = $1;
}
;
string : value
{
if ($1.type != dtype_string) {
parse_error(_("Expected string, but found %s"),
type_to_string($1.type));
/* Make sure we return something usable: */
$$ = string_alloc("ERROR", 5);
} else
$$ = $1.v.literal;
}
;
matches : MATCHES
{
$$.locus = $1;
$$.qualifier = 0;
}
| MXMATCHES
{
$$.locus = $1;
$$.qualifier = QUALIFIER_MX;
}
;
fnmatches : FNMATCHES
{
$$.locus = $1;
$$.qualifier = 0;
}
| MXFNMATCHES
{
$$.locus = $1;
$$.qualifier = QUALIFIER_MX;
}
;
number : NUMBER
| CODE
{
char *p;
$$ = strtol($1->text, &p, 10);
if (*p) {
/* should not happen */
parse_error(_("Invalid number (near `%s')"), p);
YYERROR;
}
}
;
/* Loop statements */
loopstmt : LOOP loop_ident opt_loop_parms DO stmtlist DONE opt_while
{
leave_loop();
$3.end_while = $7;
$$ = alloc_node(node_type_loop, &$1);
$3.body = $5.head;
$3.ident = $2;
$$->v.loop = $3;
}
;
loop_ident : opt_ident
{
enter_loop($1, NULL, NULL);
}
;
opt_ident : /* empty */
{
$$ = NULL;
}
| IDENTIFIER
;
opt_loop_parms: /* empty */
{
memset(&$$, 0, sizeof $$);
}
| loop_parm_list
;
loop_parm_list: loop_parm
{
memset(&$$, 0, sizeof $$);
switch ($1.kw) {
case 0:
$$.stmt = $1.expr;
break;
case FOR:
$$.for_stmt = $1.expr;
break;
case WHILE:
$$.beg_while = $1.expr;
break;
default:
abort();
}
}
| loop_parm_list ',' loop_parm
{
switch ($3.kw) {
case 0:
if ($$.stmt)
parse_error(_("Duplicate loop increment"));
$$.stmt = $3.expr;
break;
case FOR:
if ($$.for_stmt)
parse_error(_("Duplicate for statement"));
$$.for_stmt = $3.expr;
break;
case WHILE:
if ($$.beg_while)
parse_error(_("Duplicate while statement"));
$$.beg_while = $3.expr;
break;
default:
abort();
}
}
;
loop_parm : stmtlist
{
$$.kw = 0;
$$.expr = $1.head;
}
| FOR stmtlist
{
$$.kw = FOR;
$$.expr = $2.head;
}
| WHILE expr
{
$$.kw = WHILE;
$$.expr = $2;
}
;
opt_while : /* empty */
{
$$ = NULL;
}
| WHILE expr
{
$$ = $2;
}
;
jumpstmt : BREAK opt_ident
{
if (!within_loop($2)) {
if ($2)
parse_error(_("No such loop: %s"),
$2->text);
parse_error(_("`break' used outside `loop'"));
YYERROR;
}
$$ = alloc_node(node_type_break, &$1);
$$->v.literal = $2;
}
| NEXT opt_ident
{
if (!within_loop($2)) {
if ($2) {
parse_error(_("No such loop: %s"), $2->text);
parse_error(_("`next' used outside `loop'"));
YYERROR;
} else {
parse_error(_("`next' is used outside `loop'; did you mean `pass'?"));
YYERROR;
}
} else {
$$ = alloc_node(node_type_next, &$1);
$$->v.literal = $2;
}
}
;
/* Expressions */
expr : NOT expr
{
$$ = alloc_node(node_type_un, &$1);
$$->v.un.opcode = unary_not;
$$->v.un.arg = cast_to(dtype_number, $2);
}
| expr EQ expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_eq;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr NE expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_ne;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr LT expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_lt;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr LE expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_le;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr GT expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_gt;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr GE expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_ge;
$$->v.bin.arg[0] = $1;
$$->v.bin.arg[1] = cast_to(node_type($1), $3);
}
| expr matches expr %prec MATCHES
{
NODE *p;
$$ = alloc_node(node_type_bin, &$2.locus);
$$->v.bin.opcode = bin_match;
$$->v.bin.qualifier = $2.qualifier;
$$->v.bin.arg[0] = cast_to(dtype_string, $1);
$$->v.bin.arg[1] = p = alloc_node(node_type_regcomp, &$2.locus);
p->v.regcomp_data.expr = cast_to(dtype_string, $3);
p->v.regcomp_data.flags = regex_flags;
}
| expr fnmatches expr %prec MATCHES
{
$$ = alloc_node(node_type_bin, &$2.locus);
$$->v.bin.opcode = bin_fnmatch;
$$->v.bin.qualifier = $2.qualifier;
$$->v.bin.arg[0] = cast_to(dtype_string, $1);
$$->v.bin.arg[1] = cast_to(dtype_string, $3);
}
| expr OR expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_or;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| expr AND expr
{
$$ = alloc_node(node_type_bin, &$2);
$$->v.bin.opcode = bin_and;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| common_expr
;
common_expr: simp_expr
| common_expr simp_expr %prec CONCAT
{
$$ = alloc_node(node_type_concat, get_locus());
$$->v.concat.arg[0] = cast_to(dtype_string, $1);
$$->v.concat.arg[1] = cast_to(dtype_string, $2);
}
;
simp_expr : atom_expr
| simp_expr '+' simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_add;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr '-' simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_sub;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr '*' simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_mul;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr '/' simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_div;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr LOGAND simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_logand;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr LOGOR simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_logor;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
| simp_expr LOGXOR simp_expr
{
$$ = alloc_node(node_type_bin, get_locus());
$$->v.bin.opcode = bin_logxor;
$$->v.bin.arg[0] = cast_to(dtype_number, $1);
$$->v.bin.arg[1] = cast_to(dtype_number, $3);
}
;
atom_expr : funcall
| '(' expr ')'
{
$$ = $2;
}
| atom
| '-' simp_expr %prec UMINUS
{
$$ = alloc_node(node_type_un, get_locus());
$$->v.un.opcode = unary_minus;
$$->v.un.arg = cast_to(dtype_number, $2);
}
| '+' simp_expr %prec UMINUS
{
$$ = $2;
}
| LOGNOT simp_expr %prec UMINUS
{
$$ = alloc_node(node_type_un, get_locus());
$$->v.un.opcode = unary_lognot;
$$->v.un.arg = cast_to(dtype_number, $2);
}
;
atom : SYMBOL
{
register_macro(state_tag, $1->text);
$$ = alloc_node(node_type_symbol, get_locus());
$$->v.literal = $1;
}
| value
{
switch ($1.type) {
case dtype_number:
$$ = alloc_node(node_type_number, get_locus());
$$->v.number = $1.v.number;
break;
case dtype_string:
$$ = alloc_node(node_type_string, get_locus());
$$->v.literal = $1.v.literal;
break;
default:
abort();
}
}
| BACKREF
{
$$ = alloc_node(node_type_backref, get_locus());
$$->v.number = $1;
}
| variable
{
$$ = alloc_node(node_type_variable, get_locus());
$$->v.var_ref.variable = $1;
$$->v.var_ref.nframes = catch_nesting;
}
| ARG
{
if ($1 > PARMCOUNT())
parse_error(_("Argument number too high"));
$$ = alloc_node(node_type_arg, get_locus());
$$->v.arg.data_type = PARMTYPE($1);
$$->v.arg.number = $1;
if (inner_context == context_function)
$$->v.arg.number += FUNC_HIDDEN_ARGS(func);
}
| ARGCOUNT
{
if (outer_context == context_function) {
if (func->optcount) {
$$ = alloc_node(node_type_arg, &$1);
$$->v.arg.data_type = dtype_number;
$$->v.arg.number = 1;
} else {
$$ = alloc_node(node_type_number, &$1);
$$->v.number = parminfo[outer_context].parmcount();
}
} else {
$$ = alloc_node(node_type_number, &$1);
$$->v.number = parminfo[outer_context].parmcount();
}
}
| '@' IDENTIFIER
{
struct variable *var = variable_lookup($2->text);
if (!var) {
parse_error(_("Variable %s is not defined"),
$2->text);
YYERROR;
}
$$ = alloc_node(node_type_offset, get_locus());
$$->v.var_ref.variable = var;
$$->v.var_ref.nframes = catch_nesting;
}
;
funcall : BUILTIN '(' arglist ')'
{
if (check_builtin_usage($1))
YYERROR;
if ($3.count < $1->parmcount - $1->optcount) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else if ($3.count > $1->parmcount && !$1->varargs) {
parse_error(_("Too many arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args =
reverse(cast_arg_list($3.head,
$1->parmcount,
$1->parmtype));
}
}
| BUILTIN '(' ')'
{
if (check_builtin_usage($1))
YYERROR;
if ($1->parmcount - $1->optcount) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args = NULL;
}
}
| BUILTIN_P expr
{
if (check_builtin_usage($1))
YYERROR;
if ($1->parmcount - $1->optcount > 1) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else if ($1->parmcount == 0) {
parse_error(_("Too many arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args = cast_arg_list($2,
$1->parmcount,
$1->parmtype);
}
}
| FUNCTION '(' arglist ')'
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, $3.count, $3.head);
if (!$$)
YYERROR;
}
| FUNCTION '(' ')'
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, 0, NULL);
if (!$$)
YYERROR;
}
| FUNCTION_P expr
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, 1, $2);
if (!$$)
YYERROR;
}
;
proccall : BUILTIN_PROC '(' arglist ')'
{
if (check_builtin_usage($1))
YYERROR;
if ($3.count < $1->parmcount - $1->optcount) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else if ($3.count > $1->parmcount) {
parse_error(_("Too many arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args = reverse(cast_arg_list($3.head,
$1->parmcount,
$1->parmtype));
}
}
| BUILTIN_PROC '(' ')'
{
if (check_builtin_usage($1))
YYERROR;
if ($1->parmcount - $1->optcount) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args = NULL;
}
}
| BUILTIN_PROC expr
{
if (check_builtin_usage($1))
YYERROR;
if ($1->parmcount - $1->optcount > 1) {
parse_error(_("Too few arguments in call to `%s'"),
$1->name);
YYERROR;
} else if ($1->parmcount == 0) {
parse_error(_("Too many arguments in call to `%s'"),
$1->name);
YYERROR;
} else {
$$ = alloc_node(node_type_builtin, get_locus());
$$->v.builtin.builtin = $1;
$$->v.builtin.args = cast_arg_list($2,
$1->parmcount,
$1->parmtype);
}
}
| FUNCTION_PROC '(' arglist ')'
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, $3.count, $3.head);
if (!$$)
YYERROR;
}
| FUNCTION_PROC '(' ')'
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, 0, NULL);
if (!$$)
YYERROR;
}
| FUNCTION_PROC expr
{
if (check_func_usage($1))
YYERROR;
$$ = function_call($1, 1, $2);
if (!$$)
YYERROR;
}
;
arglist : expr
{
$1->next = NULL;
$$.head = $$.tail = $1;
$$.count = 1;
}
| arglist ',' expr
{
$1.tail->next = $3;
$1.tail = $3;
$1.count++;
$$ = $1;
}
;
variable : VARIABLE
{
$$ = variable_lookup($1->text);
if (!$$) {
parse_error(_("Variable %s is not defined"),
$1->text);
YYERROR;
}
add_xref($$, get_locus());
}
;
catch : CATCH catchlist DO
{ $$ = inner_context;
inner_context = context_catch;
catch_nesting++; }
stmtlist DONE
{
int i;
struct valist *p;
inner_context = $4;
catch_nesting--;
$$ = alloc_node(node_type_catch, &$1);
for (i = 0, p = $2.head; p; p = p->next)
i++;
$$->v.catch.count = i;
$$->v.catch.exmask = 0;
$$->v.catch.context = outer_context;/*??*/
for (i = 0, p = $2.head; p; p = p->next, i++) {
if (p->value.type != dtype_number) {
parse_error_locus(&$1,
_("Expected numeric value, but found `%s'"),
p->value.v.literal->text);
continue;
}
$$->v.catch.exmask |= EXMASK(p->value.v.number);
if (outer_context == context_function)
func->exmask |= EXMASK(p->value.v.number);
}
$$->v.catch.node = $5.head;
}
;
catchlist : '*'
{
int i;
$$.head = $$.tail = NULL;
for (i = 0; i < mf_status_count; i++) {
struct valist *p = xmalloc(sizeof *p);
p->next = NULL;
p->value.type = dtype_number;
p->value.v.number = i;
if (!$$.head)
$$.head = $$.tail = p;
else {
$$.tail->next = p;
$$.tail = p;
}
}
}
| valist
;
throw : THROW value expr
{
$$ = alloc_node(node_type_throw, &$1);
if ($2.type != dtype_number)
parse_error(_("Exception code not a number"));
if ($2.v.number > mf_status_count)
parse_error(_("Invalid exception number: %lu"),
$2.v.number);
$$->v.throw.code = $2.v.number;
$$->v.throw.expr = cast_to(dtype_string, $3);
}
;
return : RETURN
{
if (!func)
parse_error(_("`return' outside of a function"));
else if (func->rettype != dtype_unspecified)
parse_error(
_("`return' with no value, in function "
"returning non-void"));
$$ = alloc_node(node_type_return, &$1);
$$->v.node = NULL;
}
| RETURN expr
{
if (!func)
parse_error(_("`return' outside of a function"));
else {
if (func->rettype == dtype_unspecified)
parse_error(
_("`return' with a value, in function "
"returning void"));
$$ = alloc_node(node_type_return, &$1);
$$->v.node = cast_to(func->rettype, $2);
}
}
;
/* *************************** */
/* ON statement */
/* *************************** */
on_cond : on pollstmt do branches DONE
{
NODE *sel, *np;
NODE *head = NULL, *tail;
const struct builtin *bi;
/* Build argument list */
if ($2.client_addr) {
head = tail = $2.client_addr;
tail = $2.email;
if (!tail) {
parse_error(_("recipient address not specified in `on poll' construct"));
YYERROR;
}
tail->next = NULL;
head->next = tail;
} else
head = tail = $2.email;
if ($2.ehlo)
np = $2.ehlo;
else {
np = alloc_node(node_type_variable, get_locus());
np->v.var_ref.variable =
variable_lookup("ehlo_domain");
np->v.var_ref.nframes = 0;
}
tail->next = np;
tail = np;
if ($2.mailfrom)
np = $2.mailfrom;
else {
np = alloc_node(node_type_variable, get_locus());
np->v.var_ref.variable =
variable_lookup("mailfrom_address");
np->v.var_ref.nframes = 0;
}
tail->next = np;
tail = np;
if ($2.client_addr)
bi = builtin_lookup("strictpoll");
else
bi = builtin_lookup("stdpoll");
sel = alloc_node(node_type_builtin, &$1);
sel->v.builtin.builtin = bi;
sel->v.builtin.args = reverse(head);
$$ = create_on_node(sel, $4.head);
}
| on funcall do branches DONE
{
$$ = create_on_node($2, $4.head);
}
;
on : ON
{
onblock(1);
}
;
do : DO
{
onblock(0);
}
;
pollstmt : POLL expr
{
struct pollarg arg;
arg.kw = FOR;
arg.expr = $2;
memset(&$$, 0, sizeof $$);
set_poll_arg(&$$, arg.kw, arg.expr);
}
| POLL expr pollarglist
{
struct pollarg arg;
arg.kw = FOR;
arg.expr = $2;
set_poll_arg(&$3, arg.kw, arg.expr);
$$ = $3;
}
| POLL pollarglist
{
$$ = $2;
}
;
pollarglist: pollarg
{
memset(&$$, 0, sizeof $$);
set_poll_arg(&$$, $1.kw, $1.expr);
}
| pollarglist pollarg
{
set_poll_arg(&$1, $2.kw, $2.expr);
$$ = $1;
}
;
pollarg : FOR expr
{
$$.kw = FOR;
$$.expr = $2;
}
| HOST expr
{
$$.kw = HOST;
$$.expr = $2;
}
| AS expr
{
$$.kw = AS;
$$.expr = $2;
}
| FROM expr
{
$$.kw = FROM;
$$.expr = $2;
}
;
branches : branch
{
$$.head = $$.tail = $1;
}
| branches branch
{
$1.tail->next = $2;
$1.tail = $2;
$$ = $1;
}
;
branch : WHEN { allow_unquoted_strings = 1; } valist ':'
{ allow_unquoted_strings = 0; } stmtlist
{
struct valist *p;
for (p = $3.head; p; p = p->next) {
if (p->value.type == dtype_string) {
parse_error("Invalid data type, expected number.");
/* Try to continue */
p->value.type = dtype_number;
p->value.v.number = 0;
}
}
$$ = xmalloc(sizeof *$$);
$$->next = NULL;
$$->locus = $1;
$$->valist = $3.head;
$$->node = $6.head;
}
;
%%
int
yyerror(char *s)
{
parse_error("%s", s);
return 0;
}
int
parse_program(char *name, int ydebug)
{
int rc;
yydebug = ydebug;
if (source(name))
return -1;
outer_context = inner_context = context_none;
catch_nesting = 0;
code_immediate(NULL); /* Reserve 0 slot */
rc = yyparse() + error_count;
return rc;
}
NODE *
alloc_node(enum node_type type, const struct locus *locus)
{
NODE *node = malloc(sizeof(*node));
if (!node) {
yyerror("Not enough memory");
abort();
}
node->type = type;
node->locus = *locus;
node->next = NULL;
return node;
}
void
free_node(NODE *node)
{
free(node);
}
void
copy_node(NODE *dest, NODE *src)
{
dest->type = src->type;
dest->locus = src->locus;
dest->v = src->v;
}
void
free_subtree(NODE *node)
{
/*FIXME*/
}
/* Print parse tree */
static void print_node_list(NODE *node, int indent);
static void print_node_list_reverse(NODE *node, int level);
static void print_node(NODE *node, int indent);
void print_stat(sfsistat stat);
static int dbg_setreply(void *data, char *code, char *xcode, char *message);
static void dbg_msgmod(void *data, struct msgmod_closure *clos);
static void
print_level(int level)
{
level *= 2;
printf("%*.*s", level, level, "");
}
static void
print_bin_op(enum bin_opcode opcode)
{
char *p;
switch (opcode) {
case bin_and:
p = "AND";
break;
case bin_or:
p = "OR";
break;
case bin_eq:
p = "EQ";
break;
case bin_ne:
p = "NE";
break;
case bin_lt:
p = "LT";
break;
case bin_le:
p = "LE";
break;
case bin_gt:
p = "GT";
break;
case bin_ge:
p = "GE";
break;
case bin_match:
p = "MATCH";
break;
case bin_fnmatch:
p = "FNMATCH";
break;
case bin_add:
p = "ADD";
break;
case bin_sub:
p = "SUB";
break;
case bin_mul:
p = "MUL";
break;
case bin_div:
p = "DIV";
break;
case bin_logand:
p = "LOGAND";
break;
case bin_logor:
p = "LOGOR";
break;
case bin_logxor:
p = "LOGXOR";
break;
default:
p = "UNKNOWN_OP";
}
printf("%s", p);
}
static void
print_quoted_string(const char *str)
{
for (; *str; str++) {
if (isprint(*str))
putchar(*str);
else {
putchar('\\');
switch (*str) {
case '\a':
putchar('a');
break;
case '\b':
putchar('b');
break;
case '\f':
putchar('f');
break;
case '\n':
putchar('n');
break;
case '\r':
putchar('r');
break;
case '\t':
putchar('t');
break;
default:
printf("%03o", *str);
}
}
}
}
struct node_drv {
void (*print) (NODE *, int);
void (*mark) (NODE *);
void (*code) (NODE *, struct locus **);
void (*optimize) (NODE *);
};
static void traverse_tree(NODE *node);
static void code_node(NODE *node);
static void code_node(NODE *node);
static void optimize_node(NODE *node);
static void optimize(NODE *node);
static void record_switch(struct switch_stmt *sw);
#include "drivers.c"
#include "node-tab.c"
struct node_drv *
find_node_drv(enum node_type type)
{
if (type >= NELEMS(nodetab)) {
parse_error(_("INTERNAL ERROR at %s:%d, "
"unexpected node type %d"),
__FILE__, __LINE__,
type);
abort();
}
return nodetab + type;
}
static void
print_node(NODE *node, int level)
{
struct node_drv *nd = find_node_drv(node->type);
if (nd->print)
nd->print(node, level);
}
static void
print_node_list(NODE *node, int level)
{
for (; node; node = node->next)
print_node(node, level);
}
static void
print_node_list_reverse(NODE *node, int level)
{
if (node) {
print_node_list_reverse(node->next, level);
print_node(node, level);
}
}
int
function_enumerator(void *sym, void *data)
{
int i;
struct function *f = sym;
printf("function %s (", f->name);
for (i = 0; i < f->parmcount; i++) {
printf("%s", type_to_string(f->parmtype[i]));
if (i < f->parmcount-1)
putchar(',');
}
putchar(')');
if (f->rettype != dtype_unspecified)
printf(" returns %s", type_to_string(f->rettype));
printf(":\n");
print_node_list(f->node, 0);
printf("END function %s\n", f->name);
return 0;
}
void
print_syntax_tree()
{
enum smtp_state tag;
printf("State handlers:\n");
printf("---------------\n");
for (tag = smtp_state_first; tag < smtp_state_count; tag++) {
if (root_node[tag]) {
printf("%s:\n", state_to_string(tag));
print_node_list(root_node[tag], 0);
putchar('\n');
}
}
printf("User functions:\n");
printf("---------------\n");
symbol_enumerate(SYM_FUNC, function_enumerator, NULL);
}
/* Cross-reference support */
struct collect_data {
struct obstack stk;
size_t count;
};
static int
variable_enumerator(void *item, void *data)
{
struct variable *var = item;
struct collect_data *p = data;
if (var->flags & (VAR_VOLATILE | VAR_REFERENCED)) {
obstack_grow(&p->stk, &var, sizeof var);
p->count++;
}
return 0;
}
int
print_locus(void *item, void *data)
{
struct locus *loc = item;
struct locus **prev = data;
int c;
if (!*prev) {
*prev = loc;
printf("%s", loc->file);
c = ':';
} else if (strcmp((*prev)->file, loc->file)) {
*prev = loc;
printf(", %s", loc->file);
c = ':';
} else
c = ',';
printf("%c%lu", c, (unsigned long) loc->line);
return 0;
}
void
print_xref_var(struct variable *var)
{
struct locus *prev = NULL;
printf("%-32.32s %6s %lu ",
var->name, type_to_string(var->type), (unsigned long)var->off);
mu_list_do(var->xref, print_locus, &prev);
printf("\n");
}
int
vp_comp(const void *a, const void *b)
{
struct variable * const *va = a, * const *vb = b;
return strcmp((*va)->name, (*vb)->name);
}
void
print_xref()
{
struct collect_data cd;
struct variable **vp;
size_t i;
obstack_init(&cd.stk);
cd.count = 0;
symbol_enumerate(SYM_VARIABLE, variable_enumerator, &cd);
printf("Cross-references:\n");
printf("-----------------\n");
vp = obstack_finish(&cd.stk);
qsort(vp, cd.count, sizeof *vp, vp_comp);
for (i = 0; i < cd.count; i++, vp++)
print_xref_var(*vp);
obstack_free(&cd.stk, NULL);
}
static mu_list_t macro_list[smtp_state_count];
void
register_macro(enum smtp_state tag, const char *macro)
{
if (!script_dump_macros)
return;
if (!macro_list[tag]) {
mu_list_create(¯o_list[tag]);
mu_list_set_comparator(macro_list[tag], compare_string);
}
/* FIXME: MU: 2nd arg should be const? */
if (mu_list_locate(macro_list[tag], (void*) macro, NULL))
mu_list_append(macro_list[tag], (void*) macro);
}
static int
print_macro(void *item, void *data)
{
int *p = data;
if (*p) {
printf(" ");
*p = 0;
} else
printf(", ");
printf("%s", (char*) item);
return 0;
}
void
print_used_macros()
{
enum smtp_state tag;
for (tag = smtp_state_first; tag < smtp_state_count; tag++) {
if (macro_list[tag]) {
int n = 1;
printf("%s", state_to_string(tag));
mu_list_do(macro_list[tag], print_macro, &n);
printf("\n");
}
}
}
/* Code generation */
static void
code_node(NODE *node)
{
if (!node)
error_count++;
else {
static struct locus *old_locus;
struct node_drv *nd = find_node_drv(node->type);
if (nd->code)
nd->code(node, &old_locus);
}
}
static void
traverse_tree(NODE *node)
{
for (; node; node = node->next)
code_node(node);
}
static void
optimize_node(NODE *node)
{
if (!node)
error_count++;
else {
struct node_drv *nd = find_node_drv(node->type);
if (nd->optimize)
nd->optimize(node);
}
}
static void
optimize(NODE *node)
{
for (; node; node = node->next)
optimize_node(node);
}
static int
optimize_tree(NODE *node)
{
if (optimization_level)
optimize(node);
return error_count;
}
static struct switch_stmt *switch_root;
static void
record_switch(struct switch_stmt *sw)
{
sw->next = switch_root;
switch_root = sw;
}
static void
mark_node(NODE *node)
{
if (!node)
error_count++;
else {
struct node_drv *nd = find_node_drv(node->type);
if (nd->mark)
nd->mark(node);
}
}
static void
mark(NODE *node)
{
for (; node; node = node->next)
mark_node(node);
}
static int
codegen(prog_counter_t *pc, NODE *node, int finalize, size_t nautos)
{
if (error_count == 0) {
*pc = code_get_counter();
jump_pc = 0;
if (nautos) {
code_op(opcode_stkalloc);
code_immediate((void*)nautos);
}
traverse_tree(node);
if (finalize)
code_op(opcode_nil);
}
return error_count;
}
static void
compile_tree(NODE *node)
{
struct node_drv *nd;
struct locus *plocus;
for (; node; node = node->next) {
switch (node->type) {
case node_type_progdecl:
case node_type_funcdecl:
nd = find_node_drv(node->type);
if (!nd->code)
abort();
nd->code(node, &plocus);
break;
default:
parse_error_locus(&node->locus,
_("INTERNAL ERROR at %s:%d, "
"unexpected node type %d"),
__FILE__, __LINE__,
node->type);
break;
}
}
}
#define skip_ws(p) while (*(p) && isspace(*(p))) (p)++;
static char *
getword(char **p, char **buf, size_t *bsize)
{
int len;
size_t size = 0;
skip_ws(*p);
if (!**p)
return NULL;
for (len = 0; (*p)[len] && !isspace((*p)[len]); len++)
;
if (!bsize)
bsize = &size;
if (len + 1 > *bsize) {
*bsize = len + 1;
*buf = xrealloc(*buf, *bsize);
}
memcpy(*buf, *p, len);
(*buf)[len] = 0;
*p += len;
return *buf;
}
enum regex_mode { regex_enable, regex_disable, regex_set };
static mf_stack_t regex_stack;
void
regex_push()
{
if (!regex_stack)
regex_stack = mf_stack_create(sizeof regex_flags, 0);
mf_stack_push(regex_stack, ®ex_flags);
}
void
regex_pop()
{
if (!regex_stack || mf_stack_pop(regex_stack, ®ex_flags))
parse_error(_("Nothing to pop"));
}
static void
pragma_regex(char *text)
{
char *p, *buf = NULL;
size_t bsize = 0;
int bit;
enum regex_mode mode = regex_set;
p = getword(&text, &buf, &bsize);
if (!p) {
parse_error(_("Invalid pragma regex"));
free(buf);
return;
}
if (strcmp(p, "push") == 0) {
regex_push();
p = getword(&text, &buf, &bsize);
} else if (strcmp(p, "pop") == 0) {
regex_pop();
p = getword(&text, &buf, &bsize);
}
while (p) {
switch (p[0]) {
case '+':
mode = regex_enable;
p++;
break;
case '-':
mode = regex_disable;
p++;
break;
case '=':
mode = regex_set;
p++;
break;
}
if (strcmp (p, REG_EXTENDED_NAME) == 0)
bit = REG_EXTENDED;
else if (strcmp (p, REG_ICASE_NAME) == 0)
bit = REG_ICASE;
else if (strcmp (p, REG_NEWLINE_NAME) == 0)
bit = REG_NEWLINE;
else {
parse_error(_("Unknown regexp flag: %s"), p);
free(buf);
return;
}
switch (mode) {
case regex_disable:
regex_flags &= ~bit;
break;
case regex_enable:
regex_flags |= bit;
break;
case regex_set:
regex_flags = bit;
break;
}
p = getword(&text, &buf, &bsize);
}
free(buf);
}
static void
pragma_option(char *text)
{
char *name = NULL;
char *value = NULL;
char *p;
int len;
if (!getword(&text, &name, NULL)) {
parse_error(_("Invalid pragma: expected option name"));
return;
}
#define EXPIRE_SUFFIX "expire-interval"
#define EXPIRE_SUFFIX_LEN (sizeof(EXPIRE_SUFFIX)-1)
#define TIMEOUT_SUFFIX "timeout"
#define TIMEOUT_SUFFIX_LEN (sizeof(TIMEOUT_SUFFIX)-1)
if (strcmp(name, "mailfrom") == 0
|| (strlen(name) >= EXPIRE_SUFFIX_LEN
&& strcmp(name + strlen(name) - EXPIRE_SUFFIX_LEN,
EXPIRE_SUFFIX) == 0)
|| (strlen(name) >= TIMEOUT_SUFFIX_LEN
&& strcmp(name + strlen(name) - TIMEOUT_SUFFIX_LEN,
TIMEOUT_SUFFIX) == 0)) {
/* Special handling for expire and timeout intervals */
value = strdup(text);
text += strlen(text);
} else if (!getword(&text, &value, NULL)) {
parse_error(_("Invalid pragma: expected option value"));
free(name);
return;
}
skip_ws(text);
if (*text) {
parse_error(_("Invalid pragma: junk after the arguments"));
/* Try to handle it anyway */
}
p = value;
skip_ws(p);
len = strlen(p);
if ((*p == '"' || *p == '\'') && p[len-1] == *p) {
p[len-1] = 0;
p++;
}
if (set_option(name, p, 0)) {
if (errno == ENOENT)
parse_error(_("Unknown pragma option"));
else
parse_error(_("Invalid pragma"));
}
free(name);
free(value);
}
static void
pragma_stacksize(char *text)
{
char *p;
stack_size = strtoul(text, &p, 0);
if (*p) {
for (; *p; p++)
if (!(isascii(*p) && isspace(*p))) {
parse_error(_("Invalid pragma: junk after stack size"));
return;
}
}
}
static int
get_interval(char *text, time_t *pinterval)
{
const char *endp;
time_t interval;
while (*text && isspace(*text))
text++;
if (!*text) {
parse_error(_("Invalid pragma: unexpected end of line"));
return 1;
}
if (parse_time_interval(text, &interval, &endp)) {
parse_error(_("Invalid pragma: unrecognized time format "
"(near `%s')"),
endp);
return 1;
}
*pinterval = interval;
return 0;
}
/* #pragma database FMTNAME file FILENAME
#pragma database FMTNAME expire-interval INTERVAL
#pragma database cache positive-expire-interval INTERVAL
#pragma database cache negative-expire-interval INTERVAL
#pragma database dns negative-expire-interval INTERVAL
*/
static void
pragma_database(char *text)
{
char *fmtname = NULL;
char *kw = NULL;
struct db_format *fmt;
if (!getword(&text, &fmtname, NULL)) {
parse_error(_("Invalid pragma: expected database name"));
return;
}
if ((fmt = db_format_lookup(fmtname)) == NULL) {
parse_error(_("Unknown database format: %s"), fmtname);
return;
}
if (!getword(&text, &kw, NULL)) {
parse_error(_("Invalid pragma: expected keyword"));
return;
}
if (strcmp(kw, "file") == 0) {
char *value = NULL;
char *p;
int len;
if (!getword(&text, &value, NULL)) {
parse_error(_("Invalid pragma: expected file name"));
return;
}
p = value;
len = strlen(p);
if ((*p == '"' || *p == '\'') && p[len-1] == *p) {
p[len-1] = 0;
p++;
}
fmt->dbname = strdup(p);
free(value);
} else if (strcmp(kw, "expire-interval") == 0) {
get_interval(text, &fmt->expire_interval);
} else if (strcmp(kw, "positive-expire-interval") == 0) {
if (fmt != cache_format) {
parse_error(_("positive-expire-interval is valid only "
"for cache database"));
return;
}
get_interval(text, &fmt->expire_interval);
} else if (strcmp(kw, "negative-expire-interval") == 0) {
if (fmt == cache_format)
get_interval(text, &negative_expire_interval);
else if (fmt == dns_cache_format)
get_interval(text, &fmt->expire_interval);
else {
parse_error(_("negative-expire-interval is valid only "
"for cache and dns databases"));
return;
}
} else {
parse_error(_("Invalid pragma: unrecognized keyword: %s"), kw);
return;
}
}
void
parse_pragma(char *text)
{
char *buf = NULL;
while (*text != '#')
text++;
++text;
skip_ws(text);
text += 6; /* "pragma" */
if (!getword(&text, &buf, NULL))
parse_error(_("Empty pragma"));
else if (strcmp(buf, "regex") == 0)
pragma_regex(text);
else if (strcmp(buf, "database") == 0)
pragma_database(text);
else if (strcmp(buf, "option") == 0)
pragma_option(text);
else if (strcmp(buf, "stacksize") == 0)
pragma_stacksize(text);
else
parse_error(_("Unknown pragma"));
free(buf);
}
/* Test run */
struct sfsistat_tab {
char *name;
sfsistat stat;
} sfsistat_tab[] = {
{ "accept", SMFIS_ACCEPT },
{ "continue", SMFIS_CONTINUE },
{ "discard", SMFIS_DISCARD },
{ "reject", SMFIS_REJECT },
{ "tempfail", SMFIS_TEMPFAIL },
{ NULL }
};
const char *
sfsistat_str(sfsistat stat)
{
struct sfsistat_tab *p;
for (p = sfsistat_tab; p->name; p++)
if (p->stat == stat)
return p->name;
return NULL;
}
void
print_stat(sfsistat stat)
{
struct sfsistat_tab *p;
for (p = sfsistat_tab; p->name; p++)
if (p->stat == stat) {
printf("%s", p->name);
return;
}
printf("%d", stat);
}
const char *
msgmod_opcode_str(enum msgmod_opcode opcode)
{
switch (opcode) {
case header_add:
return "ADD HEADER";
case header_replace:
return "REPLACE HEADER";
case header_delete:
return "DELETE HEADER";
case header_insert:
return "INSERT HEADER";
case rcpt_add:
return "ADD RECIPIENT";
case rcpt_delete:
return "DELETE RECIPIENT";
case quarantine:
return "QUARANTINE";
}
return "UNKNOWN HEADER COMMAND";
}
static int
dbg_setreply(void *data, char *code, char *xcode, char *message)
{
if (code) {
printf("SET REPLY %s", code);
if (xcode)
printf(" %s", xcode);
if (message)
printf(" %s", message);
printf("\n");
}
return 0;
}
static void
dbg_msgmod(void *data, struct msgmod_closure *clos)
{
printf("%s %s: %s %u\n", msgmod_opcode_str(clos->opcode),
clos->name, SP(clos->value), clos->idx);
}
static char *
dbg_dict_getsym (void *data, char *str)
{
return dict_getsym ((mu_assoc_t)data, str);
}
void
mailfromd_test(int argc, char **argv)
{
int i, rc;
mu_assoc_t dict = NULL;
eval_environ_t env;
char *p, *end;
long n;
sfsistat status;
char *args[9] = {0,0,0,0,0,0,0,0,0};
dict_init(&dict);
env = create_environment(NULL,
dbg_dict_getsym, dbg_setreply, dbg_msgmod,
dict);
xeval(env, smtp_state_begin);
env_init(env);
for (i = 0; i < argc; i++) {
if (p = strchr(argv[i], '=')) {
char *ident = argv[i];
*p++ = 0;
if (isdigit(*ident) && *ident != 0
&& ident[1] == 0)
args[*ident - '0' - 1] = p;
else
dict_install(dict, argv[i], p);
}
}
for (i = state_parms[test_state].cnt; i--; ) {
switch (state_parms[test_state].types[i]) {
case dtype_string:
env_push_string(env, args[i] ? args[i] : "");
break;
case dtype_number:
n = strtol(p, &end, 0);
if (*end)
mu_error(_("$%d is not a number"), i+1);
env_push_number(env, n);
break;
default:
abort();
}
}
env_make_frame(env);
rc = xeval(env, test_state);
env_leave_frame(env, state_parms[test_state].cnt);
env_final_gc(env);
xeval(env, smtp_state_end);
status = environment_get_status(env);
printf("State %s: ", state_to_string(test_state));
print_stat(status);
printf("\n");
destroy_environment(env);
}
void
set_poll_arg(struct poll_data *poll, int kw, NODE *expr)
{
switch (kw) {
case FOR:
poll->email = expr;
break;
case HOST:
poll->client_addr = expr;
break;
case AS:
poll->mailfrom = expr;
break;
case FROM:
poll->ehlo = expr;
break;
default:
abort();
}
}
int
convert_rate(const char *arg, double *rate)
{
double count;
char *p;
count = strtod(arg, &p);
if (*p) {
time_t div;
const char *endp;
while (*p && isspace(*p))
p++;
if (isalpha(*p)) {
if (strlen(p) > 3
&& memcmp(p, "per", 3) == 0 && isspace(p[3]))
p += 3;
} else if (ispunct(*p))
p++;
if (parse_time_interval(p, &div, &endp)) {
mu_error(_("Unrecognized time format (near `%s')"),
endp);
return 1;
}
count /= div;
}
*rate = count;
return 0;
}
static struct tagtable {
char *name;
enum smtp_state tag;
} tagtable[] = {
{ "none", smtp_state_none },
{ "begin", smtp_state_begin },
{ "connect", smtp_state_connect },
{ "helo", smtp_state_helo },
{ "envfrom", smtp_state_envfrom },
{ "envrcpt", smtp_state_envrcpt },
{ "data", smtp_state_data },
{ "header", smtp_state_header },
{ "eoh", smtp_state_eoh },
{ "body", smtp_state_body },
{ "eom", smtp_state_eom },
{ "end", smtp_state_end },
};
enum smtp_state
string_to_state(const char *name)
{
struct tagtable *p;
for (p = tagtable; p < tagtable + sizeof tagtable/sizeof tagtable[0];
p++)
if (strcasecmp (p->name, name) == 0)
return p->tag;
return smtp_state_none;
}
const char *
state_to_string(enum smtp_state state)
{
if (state < sizeof tagtable/sizeof tagtable[0])
return tagtable[state].name;
abort();
}
struct variable *
builtin_variable_install(const char *name, data_type_t type, unsigned flags)
{
struct variable *var = variable_install(name);
var->type = type;
var->flags = flags;
var->off = variable_count++;
return var;
}
static NODE *
_reverse(NODE *list, NODE **root)
{
NODE *next;
if (list->next == NULL) {
*root = list;
return list;
}
next = _reverse(list->next, root);
next->next = list;
list->next = NULL;
return list;
}
NODE *
reverse(NODE *in)
{
NODE *root;
if (!in)
return in;
_reverse(in, &root);
return root;
}
static NODE *
create_asgn_node(struct variable *var, NODE *expr, const struct locus *loc)
{
NODE *node;
data_type_t t = node_type(expr);
if (t == dtype_unspecified) {
parse_error(_("Unspecified value not ignored as it should be"));
return NULL;
}
node = alloc_node(node_type_asgn, loc);
node->v.asgn.var = var;
node->v.asgn.nframes = catch_nesting;
node->v.asgn.node = cast_to(var->type, expr);
return node;
}
static NODE *
create_on_node(NODE *sel, struct case_stmt *cases)
{
struct case_stmt *cur;
unsigned mask = 0;
char nbuf[NUMERIC_BUFSIZE_BOUND];
struct function *fp;
const char *base_name;
char *fname;
size_t size;
int count = 0;
int argc;
NODE *args;
data_type_t *ptypes, rettype;
NODE *call_node, *retnode;
/* Create a list of exceptions to catch */
for (cur = cases; cur; cur = cur->next) {
struct valist *vp;
for (vp = cur->valist; vp; vp = vp->next) {
mask |= EXMASK(vp->value.v.number);
count++;
}
}
/* Check if we already have a wrapper function */
snprintf(nbuf, sizeof nbuf, "%u", mask);
switch (sel->type) {
case node_type_builtin:
base_name = sel->v.builtin.builtin->name;
argc = sel->v.builtin.builtin->parmcount;
args = sel->v.builtin.args;
ptypes = sel->v.builtin.builtin->parmtype;
rettype = sel->v.builtin.builtin->rettype;
break;
case node_type_call:
base_name = sel->v.call.func->name;
argc = sel->v.call.func->parmcount;
args = sel->v.call.args;
ptypes = sel->v.call.func->parmtype;
rettype = sel->v.call.func->rettype;
break;
default:
abort();
}
if (rettype == dtype_unspecified) {
parse_error_locus(&sel->locus,
_("Function `%s' does not return any value"),
base_name);
return NULL;
}
size = 2 + strlen(base_name) + 1 + strlen(nbuf) + 1;
fname = xmalloc(size);
strcpy(fname, "$");
strcat(fname, base_name);
strcat(fname, "_");
strcat(fname, nbuf);
strcat(fname, "$");
fp = function_lookup(fname);
if (!fp) {
/* Build and register the function */
NODE *np, *catch_node;
NODE *head, *tail;
int i;
/* Create the catch statement */
np = alloc_node(node_type_catch, get_locus());
np->v.catch.count = count;
np->v.catch.exmask = 0;
np->v.catch.context = context_function;
for (cur = cases; cur; cur = cur->next) {
struct valist *vp;
for (i = 0, vp = cur->valist; vp; vp = vp->next, i++)
np->v.catch.exmask |= EXMASK(vp->value.v.number);
}
catch_node = np;
np = alloc_node(node_type_return, get_locus());
np->v.node = alloc_node(node_type_arg, get_locus());
np->v.node->v.arg.number = 1;
np->v.node->v.arg.data_type = PARMTYPE(1);
catch_node->v.catch.node = np;
/* Build call arguments */
head = tail = NULL;
for (i = 1; i <= argc; i++) {
np = alloc_node(node_type_arg, get_locus());
/* FIXME: Verify if this is always correct */
np->v.arg.data_type = ptypes[i-1];
np->v.arg.number = i;
if (!head)
head = tail = np;
else {
tail->next = np;
tail = np;
}
}
np = alloc_node(node_type_return, get_locus());
np->v.node = sel; /* FIXME: create a copy */
switch (np->v.node->type) {
case node_type_builtin:
np->v.node->v.builtin.args = reverse(head);
break;
case node_type_call:
np->v.node->v.call.args = reverse(head);
break;
default:
abort();
}
catch_node->next = np;
fp = function_install(fname, argc, 0, ptypes,
rettype,
get_locus());
fp->exmask = mask;
fp->node = catch_node;
np = declare_function(fp, &fp->locus, 0);
if (genstmt.head == NULL)
genstmt.head = np;
else
genstmt.tail->next = np;
genstmt.tail = np;
}
free(fname);
call_node = alloc_node(node_type_call, get_locus());
call_node->v.call.func = fp;
call_node->v.call.args = args;
retnode = alloc_node(node_type_switch, get_locus());
retnode->v.switch_stmt.node = call_node;
retnode->v.switch_stmt.cases = cases;
return retnode;
}
NODE *
function_call(struct function *function, size_t count, NODE *subtree)
{
NODE *np = NULL;
if (count < function->parmcount - function->optcount) {
parse_error(_("Too few arguments in call to `%s'"),
function->name);
} else if (count > function->parmcount) {
parse_error(_("Too many arguments in call to `%s'"),
function->name);
} else {
np = alloc_node(node_type_call, get_locus());
np->v.call.func = function;
np->v.call.args = reverse(cast_arg_list(subtree,
function->parmcount,
function->parmtype));
}
return np;
}
data_type_t
node_type(NODE *node)
{
switch (node->type) {
case node_type_string:
case node_type_symbol:
return dtype_string;
case node_type_number:
return dtype_number;
case node_type_if:
return dtype_unspecified;
case node_type_bin:
return dtype_number;
case node_type_un:
return dtype_number;
case node_type_builtin:
return node->v.builtin.builtin->rettype;
case node_type_concat:
return dtype_string;
case node_type_variable:
return node->v.var_ref.variable->type;
case node_type_arg:
return node->v.arg.data_type;
case node_type_call:
return node->v.call.func->rettype;
case node_type_return:
if (node->v.node)
return node_type(node->v.node);
break;
case node_type_backref:
return dtype_string;
case node_type_cast:
return node->v.cast.data_type;
case node_type_offset:
return dtype_number;
case node_type_result:
case node_type_header:
case node_type_asgn:
case node_type_regex:
case node_type_regcomp:
case node_type_catch:
case node_type_throw:
case node_type_echo:
case node_type_switch:
case node_type_funcdecl:
case node_type_progdecl:
case node_type_noop:
case node_type_next:
case node_type_break:
case node_type_loop:
case max_node_type:
break;
}
return dtype_unspecified;
}
NODE *
cast_to(data_type_t type, NODE *node)
{
NODE *np;
data_type_t ntype = node_type(node);
switch (ntype) {
case dtype_string:
case dtype_number:
if (type == ntype)
return node;
break;
case dtype_unspecified:
parse_error(_("Cannot convert %s to %s"), type_to_string(ntype),
type_to_string(type));
return NULL;
default:
abort();
}
np = alloc_node(node_type_cast, get_locus());
np->v.cast.data_type = type;
np->v.cast.node = node;
node->next = NULL;
return np;
}
NODE *
cast_arg_list(NODE *args, size_t parmc, data_type_t *parmtype)
{
NODE *head = NULL, *tail = NULL;
while (args && parmc--) {
NODE *next = args->next;
NODE *p = cast_to(*parmtype, args);
if (head)
tail->next = p;
else
head = p;
tail = p;
args = next;
parmtype++;
}
return head;
}
void
add_xref(struct variable *var, const struct locus *locus)
{
if (script_dump_xref) {
struct locus *elt = xmalloc(sizeof *elt);
if (!var->xref)
mu_list_create(&var->xref);
*elt = *locus;
mu_list_append(var->xref, elt);
}
}
struct variable *
vardecl(const char *name, data_type_t type, storage_class_t sc,
struct locus *loc)
{
struct variable *var;
if (type == dtype_unspecified) {
parse_error(_("Cannot define variable of unspecified type"));
return NULL;
}
var = variable_install(name);
if (var->type == dtype_unspecified) {
/* the variable has just been added: go straight to
initializing it */;
} else if (sc != var->storage_class) {
struct variable *vp;
switch (sc) {
case storage_extern:
parse_error(_("INTERNAL ERROR at %s:%d, declaring %s %s"),
__FILE__, __LINE__,
storage_class_str(sc), name);
abort();
case storage_auto:
if (var->storage_class == storage_param) {
parse_warning(_("Automatic variable `%s' "
"is shadowing a parameter"),
var->name);
} else
parse_warning(_("Automatic variable `%s' "
"is shadowing a global"),
var->name);
unregister_auto(var);
break;
case storage_param:
parse_warning(_("Parameter `%s' is shadowing a "
"global"),
name);
}
/* Do the shadowing */
vp = variable_replace(var, NULL);
vp->shadowed = var;
var = vp;
} else {
switch (sc) {
case storage_extern:
if (var->type != type) {
parse_error(_("Redeclaring `%s' as different "
"data type"),
name);
parse_error_locus(&var->locus,
_("This is the location of the "
"previous definition"));
return NULL;
}
break;
case storage_auto:
if (var->type != type) {
parse_error(_("Redeclaring `%s' as different "
"data type"),
name);
parse_error_locus(&var->locus,
_("This is the location of the "
"previous definition"));
return NULL;
} else {
parse_error(_("Duplicate variable: %s"), name);
return NULL;
}
break;
case storage_param:
parse_error(_("Duplicate parameter: %s"), name);
return NULL;
}
}
var->type = type;
var->storage_class = sc;
switch (sc) {
case storage_extern:
add_xref(var, loc ? loc : get_locus());
break;
case storage_auto:
case storage_param:
register_auto(var);
}
var->locus = *get_locus();
return var;
}
static int
cast_value(data_type_t type, struct value *value)
{
if (type != value->type) {
char buf[NUMERIC_BUFSIZE_BOUND];
char *p;
switch (type) {
default:
abort();
case dtype_string:
snprintf(buf, sizeof buf, "%ld", value->v.number);
value->v.literal = string_alloc(buf, strlen(buf));
break;
case dtype_number:
value->v.number = strtol(value->v.literal->text,
&p, 10);
if (*p) {
parse_error(_("Cannot convert `%s' to number"),
value->v.literal->text);
return 1;
}
break;
}
value->type = type;
}
return 0;
}
static struct variable *
externdecl(const char *name, struct value *value, struct locus *loc)
{
struct variable *var = vardecl(name, value->type, storage_extern, loc);
if (!var)
return NULL;
if (initialize_variable(var, value, loc))
return NULL;
return var;
}
struct deferred_decl {
struct deferred_decl *next;
struct literal *name;
struct value value;
};
struct deferred_decl *deferred_decl;
void
defer_initialize_variable(char *arg, char *val)
{
struct deferred_decl *p;
struct literal *name = literal_lookup(arg);
for (p = deferred_decl; p; p = p->next)
if (p->name == name) {
parse_warning_locus(NULL, _("Redefining variable %s"),
name->text);
p->value.type = dtype_string;
p->value.v.literal = literal_lookup(val);
return;
}
p = xmalloc(sizeof *p);
p->name = name;
p->value.type = dtype_string;
p->value.v.literal = literal_lookup(val);
p->next = deferred_decl;
deferred_decl = p;
}
static void
apply_deferred_init()
{
struct deferred_decl *p;
for (p = deferred_decl; p; p = p->next) {
struct variable *var = variable_lookup(p->name->text);
if (!var) {
mu_error(_(": Warning: no such variable: %s"),
p->name->text);
continue;
}
if (initialize_variable(var, &p->value, get_locus()))
mu_error(_("Error initialising variable %s: incompatible types"),
p->name->text);
}
}
struct declvar {
struct declvar *next;
struct locus locus;
struct variable *var;
struct value val;
};
static struct declvar *declvar;
int
initialize_variable(struct variable *var, struct value *val,
const struct locus *locus)
{
struct declvar *dv;
if (cast_value(var->type, val))
return 1;
for (dv = declvar; dv; dv = dv->next)
if (dv->var == var) {
parse_warning(_("Variable `%s' already initialized"),
var->name);
dv->locus = *locus;
dv->val = *val;
return 0;
}
dv = xmalloc(sizeof *dv);
dv->next = declvar;
dv->var = var;
dv->locus = *locus;
dv->val = *val;
declvar = dv;
return 0;
}
void
ensure_initialized_variable(const char *name, struct value *val)
{
struct declvar *dv;
struct variable *var = variable_lookup(name);
if (!var)
mu_error(_("INTERNAL ERROR at %s:%d: variable to be "
"initialized is not declared"),
__FILE__, __LINE__);
if (var->type != val->type)
mu_error(_("INTERNAL ERROR at %s:%d: variable to be "
"initialized has wrong type"),
__FILE__, __LINE__);
for (dv = declvar; dv; dv = dv->next)
if (dv->var == var)
return;
dv = xmalloc(sizeof *dv);
dv->var = var;
dv->val = *val;
dv->next = declvar;
declvar = dv;
}
static int
_ds_variable_fun(void *sym, void *data)
{
struct variable *var = sym;
if (!(var->flags & VAR_VOLATILE) && (var->flags & VAR_REFERENCED))
var->off = variable_count++;
if ((var->flags & (VAR_VOLATILE | VAR_REFERENCED))
&& var->type == dtype_string)
dataseg_reloc_count++;
return 0;
}
static int
_ds_reloc_fun(void *sym, void *data)
{
struct variable *var = sym;
size_t *pi = data;
if (var->flags & (VAR_VOLATILE | VAR_REFERENCED)
&& var->type == dtype_string)
dataseg_reloc[(*pi)++] = var->off;
return 0;
}
static int
_ds_literal_count_fun(void *sym, void *data)
{
struct literal *lit = sym;
size_t *offset = data;
if (!(lit->flags & VAR_VOLATILE) && (lit->flags & VAR_REFERENCED)) {
lit->off = *offset;
*offset += B2STACK(strlen(lit->text) + 1);
}
return 0;
}
static int
_ds_literal_copy_fun(void *sym, void *data)
{
struct literal *lit = sym;
if (!(lit->flags & VAR_VOLATILE) && (lit->flags & VAR_REFERENCED))
strcpy((char*)(dataseg + lit->off), lit->text);
return 0;
}
static void
dataseg_layout()
{
struct declvar *dv;
size_t i;
struct switch_stmt *sw;
/* Count used variables and estimate the number of relocations
needed */
dataseg_reloc_count = 0;
symbol_enumerate(SYM_VARIABLE, _ds_variable_fun, NULL);
/* Mark literals used to initialize variables as referenced */
for (dv = declvar; dv; dv = dv->next) {
if ((dv->var->flags & (VAR_VOLATILE | VAR_REFERENCED))
&& dv->var->type == dtype_string) {
dv->val.v.literal->flags |= VAR_REFERENCED;
}
}
datasize = variable_count;
/* Count referenced literals and adjust the data size */
symbol_enumerate(SYM_LITERAL, _ds_literal_count_fun, &datasize);
/* Account for switch translation tables */
for (sw = switch_root; sw; sw = sw->next) {
sw->off = datasize;
datasize += sw->tabsize;
}
/* Allocate data segment and relocation table */
dataseg = xcalloc(datasize, sizeof(STKVAL));
dataseg_reloc = xcalloc(dataseg_reloc_count, sizeof *dataseg_reloc);
/* Fill relocation table */
i = 0;
symbol_enumerate(SYM_VARIABLE, _ds_reloc_fun, &i);
/* Initialize variables */
for (dv = declvar; dv; dv = dv->next) {
if (dv->var->flags & (VAR_VOLATILE | VAR_REFERENCED)) {
switch (dv->var->type) {
case dtype_string:
dataseg[dv->var->off] =
(STKVAL) dv->val.v.literal->off;
break;
case dtype_number:
dataseg[dv->var->off] =
(STKVAL) dv->val.v.number;
break;
default:
abort();
}
}
}
/* Place literals */
symbol_enumerate(SYM_LITERAL, _ds_literal_copy_fun, NULL);
}
static int
_regex_compile_fun(void *sym, void *data)
{
struct literal *lit = sym;
if (lit->regex) {
struct sym_regex *rp;
for (rp = lit->regex; rp; rp = rp->next)
register_regex(rp);
}
return 0;
}
void
regex_layout()
{
symbol_enumerate(SYM_LITERAL, _regex_compile_fun, NULL);
finalize_regex();
}
static struct variable *auto_list;
static void
register_auto(struct variable *var)
{
var->next = auto_list;
auto_list = var;
}
static void
unregister_auto(struct variable *var)
{
struct variable *p = auto_list, *prev = NULL;
while (p) {
struct variable *next = p->next;
if (p == var) {
if (prev)
prev->next = next;
else
auto_list = next;
p->next = NULL;
return;
}
prev = p;
p = next;
}
}
static size_t
forget_autos(size_t nparam, size_t auto_count, size_t hidden_arg)
{
size_t param_count = 0;
struct variable *var = auto_list;
while (var) {
struct variable *next = var->next;
switch (var->storage_class) {
case storage_auto:
var->off = auto_count++;
break;
case storage_param:
var->off = nparam - param_count++;
var->ord = var->off - (hidden_arg ? 1 : 0) - 1;
break;
default:
abort();
}
while (var->storage_class != storage_extern) {
struct variable *shadowed = var->shadowed;
if (!shadowed) {
find_and_remove(SYM_VARIABLE, var->name);
break;
}
var->shadowed = NULL;
var = variable_replace(var, shadowed);
}
var = next;
}
auto_list = NULL;
return auto_count;
}
const char *
storage_class_str(storage_class_t sc)
{
switch (sc) {
case storage_extern:
return "extern";
case storage_auto:
return "auto";
case storage_param:
return "param";
}
return "unknown?";
}
const char *
function_name()
{
switch (outer_context) {
case context_function:
return func->name;
case context_handler:
return state_to_string(state_tag);
default:
return "";
}
}
NODE *
declare_function(struct function *func, struct locus *loc, size_t nautos)
{
NODE *node = alloc_node(node_type_funcdecl, loc);
node->v.funcdecl.func = func;
node->v.funcdecl.auto_count = nautos;
node->v.funcdecl.tree = func->node;
return node;
}