/* * Changes by Gunnar Ritter, Freiburg i. Br., Germany, November 2002. * * Sccsid @(#)regnfa.c 1.8 (gritter) 2/6/05 */ /* UNIX(R) Regular Expresssion Library * * Note: Code is released under the GNU LGPL * * Copyright (C) 2001 Caldera International, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to: * Free Software Foundation, Inc. * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* #include "synonyms.h" */ #include #include #include "re.h" #include #include typedef unsigned char Uchar; typedef unsigned short Ushort; /* * Nondeterministic Finite Automata. */ typedef struct t_graph Graph; struct t_graph { union { Graph *ptr; Info info; } alt; Graph *next; w_type op; }; typedef struct t_stack Stack; struct t_stack { Stack *link; /* simplifies cleanup */ Stack *prev; /* covered states */ Graph *wasgp; /* node associated with this state */ const Uchar *str; /* saved position in the string */ Ushort cnt; /* ROP_BRACE: traversal count */ }; /* * A Context holds all the information needed for each * potential path through the NFA graph. */ typedef struct t_ctxt Context; struct t_ctxt { Context *link; /* simplifies cleanup */ Context *next; /* singly linked */ Stack *sp; /* nested counts */ Graph *gp; /* starting node */ Graph *wasgp; /* node associated with this state */ const Uchar *str; /* saved position in the string */ Ushort cnt; /* ROP_BRACE: traversal count */ size_t nset; /* length of rm[] that is currently set */ regmatch_t rm[1]; /* enough to cover re_nsub+1 (np->rmlen) */ }; struct re_nfa_ /*Nfa*/ { Graph *gp; /* entire NFA */ Stack *sp; /* unused Stacks */ Stack *allsp; /* linked Stacks (for cleanup) */ Context *allcp; /* linked Contexts (for cleanup) */ Context *cur; /* Contexts to be continued now */ Context *step; /* Contexts waiting for a step of the NFA */ Context *avail; /* unused Contexts */ Context **ecur; /* ends cur list of Contexts */ Context **estp; /* ends step list of Contexts */ size_t rmlen; /* length of rm[] in each Context */ size_t rmmin; /* minimum length needed */ size_t used; /* length used for this libuxre_regnfaexec() */ w_type beg; /* nonzero for fixed char initial node NFAs */ }; #define ROP_MTOR ROP_CAT /* ROP_OR, except might be empty loop */ /* * Depth first traversal. * Make a singly linked list (in alt.ptr) of the graph's nodes. * Must toss any ROP_BKTs, too, since "alt" is overwritten. */ static void deltolist(Graph *gp, Graph **list) { Graph *ptr; if ((ptr = gp->next) != 0) /* first time */ { gp->next = 0; if (gp->op == ROP_OR || gp->op == ROP_MTOR) deltolist(gp->alt.ptr, list); deltolist(ptr, list); if (gp->op == ROP_BKT) { libuxre_bktfree(gp->alt.info.bkt); free(gp->alt.info.bkt); } } else if (gp->op == ROP_END) gp->op = ROP_NOP; else return; gp->alt.ptr = *list; *list = gp; } /* * After the list is turned into a linked list, * walk that list freeing the nodes. */ static void delgraph(Graph *gp) { Graph *gp2, end; gp2 = &end; deltolist(gp, &gp2); while ((gp = gp2) != &end) { gp2 = gp->alt.ptr; free(gp); } } /* * Depth first traversal. * Look for ROP_NOPs and prune them from the graph. * Chain them all together on *nop's list. */ static Graph * nopskip(Graph *gp, Graph **nop) { Graph *ptr; if ((ptr = gp->next) != 0) /* might have yet to do this subgraph */ { if (gp->op == ROP_NOP) { if (gp->alt.ptr != 0) /* touched */ return gp->next; /* already did it */ gp->alt.ptr = *nop; *nop = gp; } gp->next = 0; /* this subgraph's pending */ if (gp->op == ROP_OR || gp->op == ROP_MTOR) gp->alt.ptr = nopskip(gp->alt.ptr, nop); gp->next = nopskip(ptr, nop); if (gp->op == ROP_NOP) return gp->next; } return gp; } /* * Postorder traversal of the parse tree. * Build a graph using "Thompson's" algorithm. * The only significant modification is the * ROP_BRACE->ROP_MTOR construction. * Returns 1 => graph might match empty * 0 => graph cannot match empty * -1 => error (in allocation) */ static int mkgraph(Tree *tp, Graph **first, Graph **last) { Graph *new = 0, *nop, *lf, *ll, *rf, *rl; int lmt, rmt = 0; if (tp->op != ROP_CAT) { if ((new = malloc(sizeof(Graph))) == 0) return 0; new->op = tp->op; /* usually */ } switch (tp->op) { case ROP_REF: new->alt.info.sub = tp->right.info.sub; *first = new; *last = new; return 1; /* safe--can't really tell */ case ROP_BKT: tp->op = ROP_BKTCOPY; /* now graph owns clean up */ /*FALLTHROUGH*/ case ROP_BKTCOPY: new->alt.info.bkt = tp->right.info.bkt; /*FALLTHROUGH*/ default: *first = new; *last = new; return 0; case ROP_EMPTY: new->op = ROP_NOP; new->alt.ptr = 0; /* untouched */ *first = new; *last = new; return 1; case ROP_OR: case ROP_CAT: lf = 0; /* in case of error */ if ((rmt = mkgraph(tp->right.ptr, &rf, &rl)) < 0) goto err; /*FALLTHROUGH*/ case ROP_STAR: case ROP_PLUS: case ROP_QUEST: case ROP_BRACE: case ROP_LP: if ((lmt = mkgraph(tp->left.ptr, &lf, &ll)) < 0) goto err; break; } /* * Note that ROP_NOP only serves as the node that reconnects * the two choices of an incoming ROP_OR or ROP_QUEST. To * prevent rewalking portions of the graph in nopskip(), * this code marks all ROP_NOP nodes as currently untouched. */ switch (tp->op) { case ROP_OR: if ((nop = malloc(sizeof(Graph))) == 0) goto err; nop->op = ROP_NOP; nop->alt.ptr = 0; /* untouched */ ll->next = nop; rl->next = nop; new->next = lf; new->alt.ptr = rf; *first = new; *last = nop; return lmt | rmt; case ROP_CAT: /* no "new" */ ll->next = rf; *first = lf; *last = rl; return lmt & rmt; case ROP_QUEST: if ((nop = malloc(sizeof(Graph))) == 0) goto err; nop->op = ROP_NOP; nop->alt.ptr = 0; /* untouched */ new->op = ROP_OR; new->next = lf; new->alt.ptr = nop; ll->next = nop; *first = new; *last = nop; return 1; case ROP_STAR: *first = new; rmt = 1; star:; new->op = lmt ? ROP_MTOR : ROP_OR; new->alt.ptr = lf; ll->next = new; *last = new; return rmt; case ROP_PLUS: *first = lf; rmt = lmt; goto star; case ROP_BRACE: if ((nop = malloc(sizeof(Graph))) == 0) goto err; nop->op = ROP_MTOR; /* going to save state anyway... */ nop->alt.ptr = lf; ll->next = new; new->next = nop; new->alt.info.num[1] = tp->right.info.num[1]; if ((new->alt.info.num[0] = tp->right.info.num[0]) == 0) { lmt = 1; *first = new; } else { new->alt.info.num[0]--; /* already done 1 */ if (new->alt.info.num[1] != BRACE_INF) new->alt.info.num[1]--; /* likewise */ *first = lf; } *last = nop; return lmt; case ROP_LP: if ((nop = malloc(sizeof(Graph))) == 0) goto err; nop->op = ROP_RP; nop->alt.info.sub = tp->right.info.sub; new->alt.info.sub = tp->right.info.sub; new->next = lf; ll->next = nop; *first = new; *last = nop; return lmt; } err:; if (KIND_ROP(tp->op) == BINARY_ROP && rf != 0) delgraph(rf); if (lf != 0) delgraph(lf); if (tp->op != ROP_CAT) free(new); return -1; } /* * Semi-preorder traversal. * Return zero if there's no simple first character * (including the operation ROP_BOL) that must always * be at the start of a matching string. * This code doesn't attempt to get an answer if the * first of the tree many be empty. */ static w_type firstop(Tree *tp) { w_type op; switch (tp->op) { case ROP_OR: if ((op = firstop(tp->left.ptr)) == 0 || op != firstop(tp->right.ptr)) { return 0; } return op; case ROP_BRACE: if (tp->right.info.num[0] == 0) return 0; /*FALLTHROUGH*/ case ROP_CAT: case ROP_PLUS: case ROP_LP: return firstop(tp->left.ptr); default: if (tp->op < 0) return 0; /*FALLTHROUGH*/ case ROP_BOL: return tp->op; } } void libuxre_regdelnfa(Nfa *np) { Context *cp, *cpn; Stack *sp, *spn; if (np->gp != 0) delgraph(np->gp); for (cp = np->allcp; cp != 0; cp = cpn) { cpn = cp->link; free(cp); } for (sp = np->allsp; sp != 0; sp = spn) { spn = sp->link; free(sp); } free(np); } LIBUXRE_STATIC int libuxre_regnfacomp(regex_t *ep, Tree *tp, Lex *lxp) { Graph *gp, end; Nfa *np; if ((np = malloc(sizeof(Nfa))) == 0) goto err; np->gp = 0; /* in case of error */ if (mkgraph(tp, &np->gp, &gp) < 0) goto err; gp->next = 0; /* nothing follows ROP_END */ np->rmlen = 0; if ((ep->re_flags & REG_NOSUB) == 0) np->rmlen = ep->re_nsub + 1; np->rmmin = 0; if (lxp->maxref != 0 && (np->rmmin = lxp->maxref + 1) > np->rmlen) np->rmlen = np->rmmin; /* * Delete all ROP_NOPs from the graph. * nopskip() disconnects them from the graph and * links them together through their alt.ptr's. */ gp = &end; np->gp = nopskip(np->gp, &gp); while (gp != &end) { Graph *gp2 = gp; gp = gp->alt.ptr; free(gp2); } np->sp = 0; np->allsp = 0; np->avail = 0; np->allcp = 0; ep->re_nfa = np; np->beg = firstop(tp); return 0; err:; if (np != 0) { if (np->gp != 0) delgraph(np->gp); free(np); } return REG_ESPACE; } static Stack * newstck(Nfa *np) { Stack *sp, **spp; int i; if ((sp = np->sp) == 0) /* get more */ { spp = &np->sp; i = 4; while ((sp = malloc(sizeof(Stack))) != 0) { sp->link = np->allsp; np->allsp = sp; *spp = sp; spp = &sp->prev; if (--i == 0) break; } *spp = 0; if ((sp = np->sp) == 0) /* first malloc failed */ return 0; } np->sp = sp->prev; return sp; } static int mkstck(Nfa *np, Context *cp, Graph *gp) { Stack *new, *sp; if (gp == 0) /* copy existing stack tail */ { /* * Hoist up top of stack. */ new = cp->sp; cp->wasgp = new->wasgp; cp->str = new->str; cp->cnt = new->cnt; cp->sp = new->prev; if ((sp = new->prev) == 0) /* only one below */ { new->prev = np->sp; np->sp = new; cp->sp = 0; return 0; } for (;;) /* copy the rest; reusing the old top */ { new->wasgp = sp->wasgp; new->str = sp->str; new->cnt = sp->cnt; if ((new->prev = sp->prev) == 0) break; if ((new->prev = newstck(np)) == 0) return REG_ESPACE; new = new->prev; sp = sp->prev; } return 0; } if (cp->wasgp != 0) /* push current down */ { if ((new = newstck(np)) == 0) return REG_ESPACE; new->prev = cp->sp; cp->sp = new; new->wasgp = cp->wasgp; new->str = cp->str; new->cnt = cp->cnt; } cp->wasgp = gp; cp->str = 0; cp->cnt = 0; return 0; } /* * Allocate a new Context (from np->avail) * and add it to the end of the current list. */ static int newctxt(Nfa *np, Context *cp, Graph *gp) { Context *new; size_t n; if ((new = np->avail) == 0) /* need more */ { Context *ncp, **cpp; int i; /* * Can't easily allocate Contexts in one call because * the alignments (given the varying length of rm[]) * are potentially nontrivial. */ n = offsetof(Context, rm) + np->rmlen * sizeof(regmatch_t); i = 4; cpp = &np->avail; while ((ncp = malloc(n)) != 0) { ncp->link = np->allcp; np->allcp = ncp; *cpp = ncp; cpp = &ncp->next; if (--i == 0) break; } *cpp = 0; if ((new = np->avail) == 0) /* first malloc failed */ return REG_ESPACE; } np->avail = new->next; new->next = 0; new->gp = gp; new->sp = 0; new->wasgp = 0; new->nset = 0; if (cp != 0) /* copy existing context information */ { if (cp->sp != 0) /* copy tail of stack */ { new->sp = cp->sp; if (mkstck(np, new, 0) != 0) return REG_ESPACE; } new->wasgp = cp->wasgp; new->str = cp->str; new->cnt = cp->cnt; /* * Copy any valid subexpression match information * from the existing context. */ if (np->used != 0 && (n = cp->nset) != 0) { regmatch_t *rmn = new->rm, *rmo = cp->rm; new->nset = n; for (;; ++rmn, ++rmo) { rmn->rm_so = rmo->rm_so; rmn->rm_eo = rmo->rm_eo; if (--n == 0) break; } } } /* * Append it to the end of the current Context list. */ *np->ecur = new; np->ecur = &new->next; return 0; } /* * Compare two byte string sequences for equality. * If REG_ICASE, walk through the strings doing * caseless comparisons of the wide characters. */ static int casecmp(const Uchar *s, Exec *xp, ssize_t i, ssize_t n, int mb_cur_max) { const Uchar *p = &xp->str[i]; const Uchar *end; w_type wc1, wc2; int k; if (strncmp((char *)s, (char *)p, n) == 0) /* try for exact match */ return 1; if ((xp->flags & REG_ICASE) == 0) return 0; /* * Walk through each testing for a match, ignoring case, * of the resulting wide characters. * Note that only "s" can run out of characters. */ end = &p[n]; do { if ((wc1 = *s++) == '\0') return 0; if (!ISONEBYTE(wc1) && (k = libuxre_mb2wc(&wc1, s)) > 0) s += k; if (!ISONEBYTE(wc2 = *p++) && (k = libuxre_mb2wc(&wc2, p)) > 0) p += k; if (wc1 != wc2) { wc1 = to_lower(wc1); wc2 = to_lower(wc2); if (wc1 != wc2) return 0; } } while (p < end); return 1; } LIBUXRE_STATIC int libuxre_regnfaexec(Nfa *np, Exec *xp) { const Uchar *s, *s1, *s2; Context *cp, *cpn; Graph *gp, *brace; Stack *sp, *spn; ssize_t rmso, len; int i, ret, mb_cur_max; w_type wc; size_t n; ret = 0; /* assume it matches */ rmso = -1; /* but no match yet */ np->cur = 0; np->step = 0; np->ecur = &np->cur; np->estp = &np->step; if ((np->used = xp->nmatch) < np->rmmin) np->used = np->rmmin; s1 = 0; /* one char back */ s = xp->str; /* current high water in string */ mb_cur_max = xp->mb_cur_max; for (;;) { /* * Get next character from string. * If the engine proper hasn't started and the engine * requires a particular character to start and this * character isn't it, try the next one. */ for (;;) { s2 = s1; s1 = s; if (!ISONEBYTE(wc = *s++) && (i = libuxre_mb2wc(&wc, s)) > 0) s += i; if (np->cur != 0 || np->beg == wc || np->beg == 0) break; if (np->beg == ROP_BOL) { if (s2 == 0 && (xp->flags & REG_NOTBOL) == 0) break; if ((xp->flags & REG_NEWLINE) == 0) goto nomatch; if (s2 != 0 && *s2 == '\n') break; } if (wc == '\0') goto nomatch; } /* * Start the engine by inserting a fresh initial context * if there's no known match as yet. (Once some match * has been found, the end is near.) */ if (rmso < 0 && newctxt(np, 0, np->gp) != 0) goto err; /* * Walk the current Contexts list, trying each. * "loop" is when a new Context is to be tried, * "again" is when the same Context continues, * but wc was not yet matched. */ cp = np->cur; loop:; gp = cp->gp; again:; switch (gp->op) { case ROP_BRACE: /* gp->next->op == ROP_MTOR */ brace = gp; gp = gp->next; goto mtor; case ROP_MTOR: brace = 0; mtor:; if (cp->wasgp != gp) /* first time */ { if (mkstck(np, cp, gp) != 0) goto err; } else if (cp->str == s) /* spinning */ goto poptonext; cp->str = s; if (brace != 0) { if (cp->cnt >= brace->alt.info.num[1]) goto poptonext; if (++cp->cnt <= brace->alt.info.num[0]) { gp = gp->alt.ptr; goto again; } if (cp->cnt > BRACE_MAX) cp->cnt = BRACE_MAX; } if (newctxt(np, cp, gp->alt.ptr) != 0) goto err; poptonext:; cp->wasgp = 0; if ((sp = cp->sp) != 0) /* pop stack */ { cp->sp = sp->prev; cp->wasgp = sp->wasgp; cp->str = sp->str; cp->cnt = sp->cnt; sp->prev = np->sp; np->sp = sp; } /*FALLTHROUGH*/ case ROP_EMPTY: tonext:; gp = gp->next; goto again; case ROP_OR: if (newctxt(np, cp, gp->alt.ptr) != 0) goto err; goto tonext; case ROP_LP: if ((n = gp->alt.info.sub) < np->used) { size_t k; cp->rm[n].rm_so = s1 - xp->str; cp->rm[n].rm_eo = -1; /* * Mark any skipped subexpressions as * failing to participate in the match. */ if ((k = cp->nset) < n) { regmatch_t *rmp = &cp->rm[k]; for (;; rmp++) { rmp->rm_so = -1; rmp->rm_eo = -1; if (++k >= n) break; } } cp->nset = n + 1; } goto tonext; case ROP_RP: if ((n = gp->alt.info.sub) < np->used) cp->rm[n].rm_eo = s1 - xp->str; goto tonext; case ROP_BOL: if (s2 == 0) { if (xp->flags & REG_NOTBOL) goto failed; } else if ((xp->flags & REG_NEWLINE) == 0 || *s2 != '\n') goto failed; goto tonext; case ROP_EOL: if (wc == '\0') { if (xp->flags & REG_NOTEOL) goto failed; } else if ((xp->flags & REG_NEWLINE) == 0 || wc != '\n') goto failed; goto tonext; default: /* character match */ if (gp->op != wc) { if ((xp->flags & REG_ICASE) == 0 || gp->op != to_lower(wc)) { goto failed; } } nextwc:; cp->gp = gp->next; tostep:; cpn = cp->next; cp->next = 0; *np->estp = cp; np->estp = &cp->next; if ((cp = cpn) == 0) break; goto loop; case ROP_NOTNL: if (wc == '\n') goto failed; /*FALLTHROUGH*/ case ROP_ANYCH: if (wc > '\0') goto nextwc; /*FALLTHROUGH*/ case ROP_NONE: failed:; cpn = cp->next; cp->next = np->avail; np->avail = cp; if ((cp = cpn) == 0) break; goto loop; case ROP_LT: if (s2 == 0) { if (xp->flags & REG_NOTBOL) goto failed; } else { w_type pwc; if (wc != '_' && !iswalnum(mb_cur_max == 1 ? btowc(wc) : wc)) goto failed; if (!ISONEBYTE(pwc = *s2)) libuxre_mb2wc(&pwc, &s2[1]); if (pwc == '_' || iswalnum(mb_cur_max== 1 ? btowc(pwc) : pwc)) goto failed; } goto tonext; case ROP_GT: if (wc == '_' || iswalnum(mb_cur_max == 1 ? btowc(wc) : wc)) goto failed; goto tonext; case ROP_BKT: case ROP_BKTCOPY: if (cp->wasgp == gp) /* rest of MCCE */ { checkspin:; if (s1 >= cp->str) /* got it all */ goto poptonext; goto tostep; } if ((i = libuxre_bktmbexec(gp->alt.info.bkt, wc, s, mb_cur_max)) < 0) goto failed; if ((n = i) == 0) /* only matched wc */ goto nextwc; spin:; if (mkstck(np, cp, gp) != 0) goto err; cp->gp = gp; /* stay here until reach past s+n */ cp->str = s + n; goto tostep; case ROP_REF: if (cp->wasgp == gp) /* rest of matched string */ goto checkspin; if ((n = gp->alt.info.sub) >= cp->nset) goto failed; if ((len = cp->rm[n].rm_eo) < 0) goto failed; if ((len -= n = cp->rm[n].rm_so) == 0) goto tonext; if (casecmp(s1, xp, n, len, mb_cur_max) == 0) goto failed; if ((n = s - s1) >= len) goto nextwc; n = len - n; goto spin; case ROP_END: /* success! */ if (xp->flags & REG_NONEMPTY) { if (s2 == 0) goto failed; } if (xp->nmatch == 0) goto match; /* * Mark any skipped subexpressions as failing to match. */ if ((n = cp->nset) < xp->nmatch) { do { cp->rm[n].rm_so = -1; cp->rm[n].rm_eo = -1; } while (++n < xp->nmatch); } /* * Note the left-most match that's longest. */ n = cp->rm[0].rm_so; if (rmso < 0 || n < rmso) { rmso = n; record:; memcpy(xp->match, cp->rm, xp->nmatch * sizeof(regmatch_t)); goto failed; } if (rmso < n || xp->match[0].rm_eo > cp->rm[0].rm_eo) goto failed; if (xp->match[0].rm_eo < cp->rm[0].rm_eo) goto record; #if 0 /* maximize the lengths of earlier LP...RPs */ /* * If both are of the same length and start * at the same point, choose the one with * a "longest submatch from left to right" * where an empty string wins over a nonmatch. */ for (n = 1; n < xp->nmatch; n++) { ssize_t nlen; /* * First, go with the choice that has any * match for subexpr n. */ len = xp->match[n].rm_eo; nlen = cp->rm[n].rm_eo; if (nlen < 0) { if (len >= 0) break; } else if (len < 0) goto record; /* * Both have a match; go with the longer. */ len -= xp->match[n].rm_so; nlen -= cp->rm[n].rm_so; if (nlen < len) break; if (nlen > len) goto record; } #else /* take LP and RP as "fence posts" and maximize earlier gaps */ /* * If both are of the same length and start * at the same point, choose the one with * the larger earlier subpatterns, in which * each rm_so and rm_eo serves as a separator. */ for (n = 1; n < xp->nmatch; n++) { ssize_t nlen; int use; if (xp->flags & REG_AVOIDNULL) { /* * This is to to satisfy POSIX.1-2001 * XBD pp. 172-173 ll. 6127-6129, whose * translation is "do not match null * expressions if there is a choice". * See also POSIX.2 interpretation #43 * in which the question was raised. * * The first subexpression of "\(x*\)*" * must thus match the string "xxx". */ use = cp->rm[n].rm_eo - cp->rm[n].rm_so >= xp->match[n].rm_eo - xp->match[n].rm_so || xp->match[n].rm_so < 0; } else use = 1; /* * Choose the rightmost ROP_LP as that * maximizes the gap from before. */ len = xp->match[n].rm_so; nlen = cp->rm[n].rm_so; if (len < nlen && use) goto record; if (len > nlen) break; /* * The ROP_LPs are at the same point: * Choose the rightmost ROP_RP. */ len = xp->match[n].rm_eo; nlen = cp->rm[n].rm_eo; if (len < nlen && use) goto record; if (len > nlen) break; } #endif goto failed; } /* * Finished the current Context list. If the input string * has been entirely scanned, we're done. Otherwise, make * the next step list current for the next character. * If the next step list was empty and there's an existing * match, that's the left-most longest. */ if (wc == '\0') { if (rmso >= 0) goto match; goto nomatch; } np->ecur = np->estp; if ((np->cur = np->step) == 0) { if (rmso >= 0) goto match; np->ecur = &np->cur; /* was pointing at step */ } np->step = 0; np->estp = &np->step; } nomatch:; ret = REG_NOMATCH; match:; np->avail = 0; for (cp = np->allcp; cp != 0; cp = cpn) { cpn = cp->link; cp->next = np->avail; np->avail = cp; } np->sp = 0; for (sp = np->allsp; sp != 0; sp = spn) { spn = sp->link; sp->prev = np->sp; np->sp = sp; } return ret; err:; ret = REG_ESPACE; goto match; }