1*698f87a4SGarrett D'Amore /* $Id: tbl_layout.c,v 1.23 2012/05/27 17:54:54 schwarze Exp $ */ 295c635efSGarrett D'Amore /* 395c635efSGarrett D'Amore * Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> 4*698f87a4SGarrett D'Amore * Copyright (c) 2012 Ingo Schwarze <schwarze@openbsd.org> 595c635efSGarrett D'Amore * 695c635efSGarrett D'Amore * Permission to use, copy, modify, and distribute this software for any 795c635efSGarrett D'Amore * purpose with or without fee is hereby granted, provided that the above 895c635efSGarrett D'Amore * copyright notice and this permission notice appear in all copies. 995c635efSGarrett D'Amore * 1095c635efSGarrett D'Amore * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1195c635efSGarrett D'Amore * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1295c635efSGarrett D'Amore * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1395c635efSGarrett D'Amore * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1495c635efSGarrett D'Amore * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1595c635efSGarrett D'Amore * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1695c635efSGarrett D'Amore * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1795c635efSGarrett D'Amore */ 1895c635efSGarrett D'Amore #ifdef HAVE_CONFIG_H 1995c635efSGarrett D'Amore #include "config.h" 2095c635efSGarrett D'Amore #endif 2195c635efSGarrett D'Amore 2295c635efSGarrett D'Amore #include <assert.h> 2395c635efSGarrett D'Amore #include <ctype.h> 2495c635efSGarrett D'Amore #include <stdlib.h> 2595c635efSGarrett D'Amore #include <string.h> 2695c635efSGarrett D'Amore #include <time.h> 2795c635efSGarrett D'Amore 2895c635efSGarrett D'Amore #include "mandoc.h" 2995c635efSGarrett D'Amore #include "libmandoc.h" 3095c635efSGarrett D'Amore #include "libroff.h" 3195c635efSGarrett D'Amore 3295c635efSGarrett D'Amore struct tbl_phrase { 3395c635efSGarrett D'Amore char name; 3495c635efSGarrett D'Amore enum tbl_cellt key; 3595c635efSGarrett D'Amore }; 3695c635efSGarrett D'Amore 3795c635efSGarrett D'Amore /* 3895c635efSGarrett D'Amore * FIXME: we can make this parse a lot nicer by, when an error is 3995c635efSGarrett D'Amore * encountered in a layout key, bailing to the next key (i.e. to the 4095c635efSGarrett D'Amore * next whitespace then continuing). 4195c635efSGarrett D'Amore */ 4295c635efSGarrett D'Amore 4395c635efSGarrett D'Amore #define KEYS_MAX 11 4495c635efSGarrett D'Amore 4595c635efSGarrett D'Amore static const struct tbl_phrase keys[KEYS_MAX] = { 4695c635efSGarrett D'Amore { 'c', TBL_CELL_CENTRE }, 4795c635efSGarrett D'Amore { 'r', TBL_CELL_RIGHT }, 4895c635efSGarrett D'Amore { 'l', TBL_CELL_LEFT }, 4995c635efSGarrett D'Amore { 'n', TBL_CELL_NUMBER }, 5095c635efSGarrett D'Amore { 's', TBL_CELL_SPAN }, 5195c635efSGarrett D'Amore { 'a', TBL_CELL_LONG }, 5295c635efSGarrett D'Amore { '^', TBL_CELL_DOWN }, 5395c635efSGarrett D'Amore { '-', TBL_CELL_HORIZ }, 5495c635efSGarrett D'Amore { '_', TBL_CELL_HORIZ }, 55*698f87a4SGarrett D'Amore { '=', TBL_CELL_DHORIZ } 5695c635efSGarrett D'Amore }; 5795c635efSGarrett D'Amore 5895c635efSGarrett D'Amore static int mods(struct tbl_node *, struct tbl_cell *, 5995c635efSGarrett D'Amore int, const char *, int *); 6095c635efSGarrett D'Amore static int cell(struct tbl_node *, struct tbl_row *, 6195c635efSGarrett D'Amore int, const char *, int *); 6295c635efSGarrett D'Amore static void row(struct tbl_node *, int, const char *, int *); 63*698f87a4SGarrett D'Amore static struct tbl_cell *cell_alloc(struct tbl_node *, struct tbl_row *, 64*698f87a4SGarrett D'Amore enum tbl_cellt, int vert); 6595c635efSGarrett D'Amore 6695c635efSGarrett D'Amore static int 6795c635efSGarrett D'Amore mods(struct tbl_node *tbl, struct tbl_cell *cp, 6895c635efSGarrett D'Amore int ln, const char *p, int *pos) 6995c635efSGarrett D'Amore { 7095c635efSGarrett D'Amore char buf[5]; 7195c635efSGarrett D'Amore int i; 7295c635efSGarrett D'Amore 7395c635efSGarrett D'Amore /* Not all types accept modifiers. */ 7495c635efSGarrett D'Amore 7595c635efSGarrett D'Amore switch (cp->pos) { 7695c635efSGarrett D'Amore case (TBL_CELL_DOWN): 7795c635efSGarrett D'Amore /* FALLTHROUGH */ 7895c635efSGarrett D'Amore case (TBL_CELL_HORIZ): 7995c635efSGarrett D'Amore /* FALLTHROUGH */ 8095c635efSGarrett D'Amore case (TBL_CELL_DHORIZ): 8195c635efSGarrett D'Amore return(1); 8295c635efSGarrett D'Amore default: 8395c635efSGarrett D'Amore break; 8495c635efSGarrett D'Amore } 8595c635efSGarrett D'Amore 8695c635efSGarrett D'Amore mod: 8795c635efSGarrett D'Amore /* 8895c635efSGarrett D'Amore * XXX: since, at least for now, modifiers are non-conflicting 8995c635efSGarrett D'Amore * (are separable by value, regardless of position), we let 9095c635efSGarrett D'Amore * modifiers come in any order. The existing tbl doesn't let 9195c635efSGarrett D'Amore * this happen. 9295c635efSGarrett D'Amore */ 9395c635efSGarrett D'Amore switch (p[*pos]) { 9495c635efSGarrett D'Amore case ('\0'): 9595c635efSGarrett D'Amore /* FALLTHROUGH */ 9695c635efSGarrett D'Amore case (' '): 9795c635efSGarrett D'Amore /* FALLTHROUGH */ 9895c635efSGarrett D'Amore case ('\t'): 9995c635efSGarrett D'Amore /* FALLTHROUGH */ 10095c635efSGarrett D'Amore case (','): 10195c635efSGarrett D'Amore /* FALLTHROUGH */ 10295c635efSGarrett D'Amore case ('.'): 10395c635efSGarrett D'Amore return(1); 10495c635efSGarrett D'Amore default: 10595c635efSGarrett D'Amore break; 10695c635efSGarrett D'Amore } 10795c635efSGarrett D'Amore 10895c635efSGarrett D'Amore /* Throw away parenthesised expression. */ 10995c635efSGarrett D'Amore 11095c635efSGarrett D'Amore if ('(' == p[*pos]) { 11195c635efSGarrett D'Amore (*pos)++; 11295c635efSGarrett D'Amore while (p[*pos] && ')' != p[*pos]) 11395c635efSGarrett D'Amore (*pos)++; 11495c635efSGarrett D'Amore if (')' == p[*pos]) { 11595c635efSGarrett D'Amore (*pos)++; 11695c635efSGarrett D'Amore goto mod; 11795c635efSGarrett D'Amore } 11895c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, 11995c635efSGarrett D'Amore tbl->parse, ln, *pos, NULL); 12095c635efSGarrett D'Amore return(0); 12195c635efSGarrett D'Amore } 12295c635efSGarrett D'Amore 12395c635efSGarrett D'Amore /* Parse numerical spacing from modifier string. */ 12495c635efSGarrett D'Amore 12595c635efSGarrett D'Amore if (isdigit((unsigned char)p[*pos])) { 12695c635efSGarrett D'Amore for (i = 0; i < 4; i++) { 12795c635efSGarrett D'Amore if ( ! isdigit((unsigned char)p[*pos + i])) 12895c635efSGarrett D'Amore break; 12995c635efSGarrett D'Amore buf[i] = p[*pos + i]; 13095c635efSGarrett D'Amore } 13195c635efSGarrett D'Amore buf[i] = '\0'; 13295c635efSGarrett D'Amore 13395c635efSGarrett D'Amore /* No greater than 4 digits. */ 13495c635efSGarrett D'Amore 13595c635efSGarrett D'Amore if (4 == i) { 13695c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 13795c635efSGarrett D'Amore ln, *pos, NULL); 13895c635efSGarrett D'Amore return(0); 13995c635efSGarrett D'Amore } 14095c635efSGarrett D'Amore 14195c635efSGarrett D'Amore *pos += i; 14295c635efSGarrett D'Amore cp->spacing = (size_t)atoi(buf); 14395c635efSGarrett D'Amore 14495c635efSGarrett D'Amore goto mod; 14595c635efSGarrett D'Amore /* NOTREACHED */ 14695c635efSGarrett D'Amore } 14795c635efSGarrett D'Amore 14895c635efSGarrett D'Amore /* TODO: GNU has many more extensions. */ 14995c635efSGarrett D'Amore 15095c635efSGarrett D'Amore switch (tolower((unsigned char)p[(*pos)++])) { 15195c635efSGarrett D'Amore case ('z'): 15295c635efSGarrett D'Amore cp->flags |= TBL_CELL_WIGN; 15395c635efSGarrett D'Amore goto mod; 15495c635efSGarrett D'Amore case ('u'): 15595c635efSGarrett D'Amore cp->flags |= TBL_CELL_UP; 15695c635efSGarrett D'Amore goto mod; 15795c635efSGarrett D'Amore case ('e'): 15895c635efSGarrett D'Amore cp->flags |= TBL_CELL_EQUAL; 15995c635efSGarrett D'Amore goto mod; 16095c635efSGarrett D'Amore case ('t'): 16195c635efSGarrett D'Amore cp->flags |= TBL_CELL_TALIGN; 16295c635efSGarrett D'Amore goto mod; 16395c635efSGarrett D'Amore case ('d'): 16495c635efSGarrett D'Amore cp->flags |= TBL_CELL_BALIGN; 16595c635efSGarrett D'Amore goto mod; 16695c635efSGarrett D'Amore case ('w'): /* XXX for now, ignore minimal column width */ 16795c635efSGarrett D'Amore goto mod; 16895c635efSGarrett D'Amore case ('f'): 16995c635efSGarrett D'Amore break; 17095c635efSGarrett D'Amore case ('r'): 17195c635efSGarrett D'Amore /* FALLTHROUGH */ 17295c635efSGarrett D'Amore case ('b'): 17395c635efSGarrett D'Amore /* FALLTHROUGH */ 17495c635efSGarrett D'Amore case ('i'): 17595c635efSGarrett D'Amore (*pos)--; 17695c635efSGarrett D'Amore break; 17795c635efSGarrett D'Amore default: 17895c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 17995c635efSGarrett D'Amore ln, *pos - 1, NULL); 18095c635efSGarrett D'Amore return(0); 18195c635efSGarrett D'Amore } 18295c635efSGarrett D'Amore 18395c635efSGarrett D'Amore switch (tolower((unsigned char)p[(*pos)++])) { 18495c635efSGarrett D'Amore case ('3'): 18595c635efSGarrett D'Amore /* FALLTHROUGH */ 18695c635efSGarrett D'Amore case ('b'): 18795c635efSGarrett D'Amore cp->flags |= TBL_CELL_BOLD; 18895c635efSGarrett D'Amore goto mod; 18995c635efSGarrett D'Amore case ('2'): 19095c635efSGarrett D'Amore /* FALLTHROUGH */ 19195c635efSGarrett D'Amore case ('i'): 19295c635efSGarrett D'Amore cp->flags |= TBL_CELL_ITALIC; 19395c635efSGarrett D'Amore goto mod; 19495c635efSGarrett D'Amore case ('1'): 19595c635efSGarrett D'Amore /* FALLTHROUGH */ 19695c635efSGarrett D'Amore case ('r'): 19795c635efSGarrett D'Amore goto mod; 19895c635efSGarrett D'Amore default: 19995c635efSGarrett D'Amore break; 20095c635efSGarrett D'Amore } 20195c635efSGarrett D'Amore 20295c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 20395c635efSGarrett D'Amore ln, *pos - 1, NULL); 20495c635efSGarrett D'Amore return(0); 20595c635efSGarrett D'Amore } 20695c635efSGarrett D'Amore 20795c635efSGarrett D'Amore static int 20895c635efSGarrett D'Amore cell(struct tbl_node *tbl, struct tbl_row *rp, 20995c635efSGarrett D'Amore int ln, const char *p, int *pos) 21095c635efSGarrett D'Amore { 211*698f87a4SGarrett D'Amore int vert, i; 21295c635efSGarrett D'Amore enum tbl_cellt c; 21395c635efSGarrett D'Amore 214*698f87a4SGarrett D'Amore /* Handle vertical lines. */ 215*698f87a4SGarrett D'Amore 216*698f87a4SGarrett D'Amore for (vert = 0; '|' == p[*pos]; ++*pos) 217*698f87a4SGarrett D'Amore vert++; 218*698f87a4SGarrett D'Amore while (' ' == p[*pos]) 219*698f87a4SGarrett D'Amore (*pos)++; 220*698f87a4SGarrett D'Amore 221*698f87a4SGarrett D'Amore /* Parse the column position (`c', `l', `r', ...). */ 22295c635efSGarrett D'Amore 22395c635efSGarrett D'Amore for (i = 0; i < KEYS_MAX; i++) 22495c635efSGarrett D'Amore if (tolower((unsigned char)p[*pos]) == keys[i].name) 22595c635efSGarrett D'Amore break; 22695c635efSGarrett D'Amore 22795c635efSGarrett D'Amore if (KEYS_MAX == i) { 22895c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 22995c635efSGarrett D'Amore ln, *pos, NULL); 23095c635efSGarrett D'Amore return(0); 23195c635efSGarrett D'Amore } 23295c635efSGarrett D'Amore 23395c635efSGarrett D'Amore c = keys[i].key; 23495c635efSGarrett D'Amore 23595c635efSGarrett D'Amore /* 23695c635efSGarrett D'Amore * If a span cell is found first, raise a warning and abort the 23795c635efSGarrett D'Amore * parse. If a span cell is found and the last layout element 23895c635efSGarrett D'Amore * isn't a "normal" layout, bail. 23995c635efSGarrett D'Amore * 24095c635efSGarrett D'Amore * FIXME: recover from this somehow? 24195c635efSGarrett D'Amore */ 24295c635efSGarrett D'Amore 24395c635efSGarrett D'Amore if (TBL_CELL_SPAN == c) { 24495c635efSGarrett D'Amore if (NULL == rp->first) { 24595c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 24695c635efSGarrett D'Amore ln, *pos, NULL); 24795c635efSGarrett D'Amore return(0); 24895c635efSGarrett D'Amore } else if (rp->last) 24995c635efSGarrett D'Amore switch (rp->last->pos) { 25095c635efSGarrett D'Amore case (TBL_CELL_HORIZ): 25195c635efSGarrett D'Amore case (TBL_CELL_DHORIZ): 25295c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, 25395c635efSGarrett D'Amore ln, *pos, NULL); 25495c635efSGarrett D'Amore return(0); 25595c635efSGarrett D'Amore default: 25695c635efSGarrett D'Amore break; 25795c635efSGarrett D'Amore } 25895c635efSGarrett D'Amore } 25995c635efSGarrett D'Amore 26095c635efSGarrett D'Amore /* 26195c635efSGarrett D'Amore * If a vertical spanner is found, we may not be in the first 26295c635efSGarrett D'Amore * row. 26395c635efSGarrett D'Amore */ 26495c635efSGarrett D'Amore 26595c635efSGarrett D'Amore if (TBL_CELL_DOWN == c && rp == tbl->first_row) { 26695c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos, NULL); 26795c635efSGarrett D'Amore return(0); 26895c635efSGarrett D'Amore } 26995c635efSGarrett D'Amore 27095c635efSGarrett D'Amore (*pos)++; 27195c635efSGarrett D'Amore 27295c635efSGarrett D'Amore /* Disallow adjacent spacers. */ 27395c635efSGarrett D'Amore 274*698f87a4SGarrett D'Amore if (vert > 2) { 27595c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLLAYOUT, tbl->parse, ln, *pos - 1, NULL); 27695c635efSGarrett D'Amore return(0); 27795c635efSGarrett D'Amore } 27895c635efSGarrett D'Amore 27995c635efSGarrett D'Amore /* Allocate cell then parse its modifiers. */ 28095c635efSGarrett D'Amore 281*698f87a4SGarrett D'Amore return(mods(tbl, cell_alloc(tbl, rp, c, vert), ln, p, pos)); 28295c635efSGarrett D'Amore } 28395c635efSGarrett D'Amore 28495c635efSGarrett D'Amore 28595c635efSGarrett D'Amore static void 28695c635efSGarrett D'Amore row(struct tbl_node *tbl, int ln, const char *p, int *pos) 28795c635efSGarrett D'Amore { 28895c635efSGarrett D'Amore struct tbl_row *rp; 28995c635efSGarrett D'Amore 29095c635efSGarrett D'Amore row: /* 29195c635efSGarrett D'Amore * EBNF describing this section: 29295c635efSGarrett D'Amore * 29395c635efSGarrett D'Amore * row ::= row_list [:space:]* [.]?[\n] 29495c635efSGarrett D'Amore * row_list ::= [:space:]* row_elem row_tail 29595c635efSGarrett D'Amore * row_tail ::= [:space:]*[,] row_list | 29695c635efSGarrett D'Amore * epsilon 29795c635efSGarrett D'Amore * row_elem ::= [\t\ ]*[:alpha:]+ 29895c635efSGarrett D'Amore */ 29995c635efSGarrett D'Amore 30095c635efSGarrett D'Amore rp = mandoc_calloc(1, sizeof(struct tbl_row)); 301*698f87a4SGarrett D'Amore if (tbl->last_row) 30295c635efSGarrett D'Amore tbl->last_row->next = rp; 303*698f87a4SGarrett D'Amore else 304*698f87a4SGarrett D'Amore tbl->first_row = rp; 30595c635efSGarrett D'Amore tbl->last_row = rp; 30695c635efSGarrett D'Amore 30795c635efSGarrett D'Amore cell: 30895c635efSGarrett D'Amore while (isspace((unsigned char)p[*pos])) 30995c635efSGarrett D'Amore (*pos)++; 31095c635efSGarrett D'Amore 31195c635efSGarrett D'Amore /* Safely exit layout context. */ 31295c635efSGarrett D'Amore 31395c635efSGarrett D'Amore if ('.' == p[*pos]) { 31495c635efSGarrett D'Amore tbl->part = TBL_PART_DATA; 31595c635efSGarrett D'Amore if (NULL == tbl->first_row) 31695c635efSGarrett D'Amore mandoc_msg(MANDOCERR_TBLNOLAYOUT, tbl->parse, 31795c635efSGarrett D'Amore ln, *pos, NULL); 31895c635efSGarrett D'Amore (*pos)++; 31995c635efSGarrett D'Amore return; 32095c635efSGarrett D'Amore } 32195c635efSGarrett D'Amore 32295c635efSGarrett D'Amore /* End (and possibly restart) a row. */ 32395c635efSGarrett D'Amore 32495c635efSGarrett D'Amore if (',' == p[*pos]) { 32595c635efSGarrett D'Amore (*pos)++; 32695c635efSGarrett D'Amore goto row; 32795c635efSGarrett D'Amore } else if ('\0' == p[*pos]) 32895c635efSGarrett D'Amore return; 32995c635efSGarrett D'Amore 33095c635efSGarrett D'Amore if ( ! cell(tbl, rp, ln, p, pos)) 33195c635efSGarrett D'Amore return; 33295c635efSGarrett D'Amore 33395c635efSGarrett D'Amore goto cell; 33495c635efSGarrett D'Amore /* NOTREACHED */ 33595c635efSGarrett D'Amore } 33695c635efSGarrett D'Amore 33795c635efSGarrett D'Amore int 33895c635efSGarrett D'Amore tbl_layout(struct tbl_node *tbl, int ln, const char *p) 33995c635efSGarrett D'Amore { 34095c635efSGarrett D'Amore int pos; 34195c635efSGarrett D'Amore 34295c635efSGarrett D'Amore pos = 0; 34395c635efSGarrett D'Amore row(tbl, ln, p, &pos); 34495c635efSGarrett D'Amore 34595c635efSGarrett D'Amore /* Always succeed. */ 34695c635efSGarrett D'Amore return(1); 34795c635efSGarrett D'Amore } 34895c635efSGarrett D'Amore 34995c635efSGarrett D'Amore static struct tbl_cell * 350*698f87a4SGarrett D'Amore cell_alloc(struct tbl_node *tbl, struct tbl_row *rp, enum tbl_cellt pos, 351*698f87a4SGarrett D'Amore int vert) 35295c635efSGarrett D'Amore { 35395c635efSGarrett D'Amore struct tbl_cell *p, *pp; 35495c635efSGarrett D'Amore struct tbl_head *h, *hp; 35595c635efSGarrett D'Amore 35695c635efSGarrett D'Amore p = mandoc_calloc(1, sizeof(struct tbl_cell)); 35795c635efSGarrett D'Amore 35895c635efSGarrett D'Amore if (NULL != (pp = rp->last)) { 359*698f87a4SGarrett D'Amore pp->next = p; 360*698f87a4SGarrett D'Amore h = pp->head->next; 361*698f87a4SGarrett D'Amore } else { 362*698f87a4SGarrett D'Amore rp->first = p; 363*698f87a4SGarrett D'Amore h = tbl->first_head; 364*698f87a4SGarrett D'Amore } 36595c635efSGarrett D'Amore rp->last = p; 36695c635efSGarrett D'Amore 36795c635efSGarrett D'Amore p->pos = pos; 368*698f87a4SGarrett D'Amore p->vert = vert; 36995c635efSGarrett D'Amore 370*698f87a4SGarrett D'Amore /* Re-use header. */ 37195c635efSGarrett D'Amore 37295c635efSGarrett D'Amore if (h) { 37395c635efSGarrett D'Amore p->head = h; 37495c635efSGarrett D'Amore return(p); 37595c635efSGarrett D'Amore } 37695c635efSGarrett D'Amore 37795c635efSGarrett D'Amore hp = mandoc_calloc(1, sizeof(struct tbl_head)); 37895c635efSGarrett D'Amore hp->ident = tbl->opts.cols++; 379*698f87a4SGarrett D'Amore hp->vert = vert; 38095c635efSGarrett D'Amore 38195c635efSGarrett D'Amore if (tbl->last_head) { 38295c635efSGarrett D'Amore hp->prev = tbl->last_head; 38395c635efSGarrett D'Amore tbl->last_head->next = hp; 38495c635efSGarrett D'Amore } else 385*698f87a4SGarrett D'Amore tbl->first_head = hp; 386*698f87a4SGarrett D'Amore tbl->last_head = hp; 38795c635efSGarrett D'Amore 38895c635efSGarrett D'Amore p->head = hp; 38995c635efSGarrett D'Amore return(p); 39095c635efSGarrett D'Amore } 391