%{ /* 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; }