/*********************************************************************** * $Id$ * Copyright 2009 Aplix Corporation. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***********************************************************************/ #include #include #include #include "comment.h" #include "entities.h" #include "lex.h" #include "misc.h" #include "node.h" #include "os.h" #include "process.h" /* struct cnode : a node in the comment parse tree */ struct cnode { struct cnode *next; struct cnode *children; struct cnode *parent; const struct cnodefuncs *funcs; const char *attrtext; const char *filename; unsigned int linenum; }; struct cnodefuncs { int indesc; /* non-zero if it outputs its own xml element that does not want to be inside */ int needpara; /* non-zero if text must be in a para node that is a child of this one */ int (*askend)(struct cnode *cnode, const struct cnodefuncs *type); void (*end)(struct cnode *cnode); void (*output)(struct cnode *cnode, unsigned int indent); }; struct paramcnode { struct cnode cn; int inout; char name[1]; }; /* struct comment : a doxygen comment */ struct comment { struct comment *next; struct node *node; unsigned int type; const char *filename; unsigned int linenum; struct cnode root; int back; /* Whether the comment refers back rather than forward. */ char *text; }; static struct node *lastidentifier; static struct comment *comments; static int incode, inhtmlblock; static struct comment *curcomment; /*********************************************************************** * htmleldescs : table of recnogized HTML elements */ #define HTMLEL_EMPTY 1 #define HTMLEL_INLINE 2 #define HTMLEL_BLOCK 4 #define HTMLEL_AUTOCLOSE 8 #define HTMLEL_LI 0x10 #define HTMLEL_DLCONTENTS 0x20 #define HTMLEL_TABLECONTENTS 0x40 #define HTMLEL_TRCONTENTS 0x80 #define HTMLEL_FLOW (HTMLEL_BLOCK | HTMLEL_INLINE) struct htmleldesc { unsigned int namelen; const char *name; unsigned int flags; unsigned int content; }; static const struct htmleldesc htmleldescs[] = { { 1, "a", HTMLEL_INLINE, 0 }, { 1, "b", HTMLEL_INLINE, 0 }, { 2, "br", HTMLEL_INLINE, HTMLEL_EMPTY }, { 3, "img", HTMLEL_INLINE, HTMLEL_EMPTY }, { 2, "dd", HTMLEL_DLCONTENTS, HTMLEL_FLOW }, { 2, "dl", HTMLEL_BLOCK, HTMLEL_DLCONTENTS }, { 2, "dt", HTMLEL_DLCONTENTS, HTMLEL_INLINE }, { 2, "em", HTMLEL_INLINE, 0 }, { 2, "li", HTMLEL_LI, HTMLEL_FLOW }, { 2, "ol", HTMLEL_BLOCK, HTMLEL_LI }, { 1, "p", HTMLEL_BLOCK, HTMLEL_INLINE }, { 2, "td", HTMLEL_TRCONTENTS | HTMLEL_AUTOCLOSE, HTMLEL_FLOW }, { 2, "th", HTMLEL_TRCONTENTS | HTMLEL_AUTOCLOSE, HTMLEL_FLOW }, { 2, "tr", HTMLEL_TABLECONTENTS | HTMLEL_AUTOCLOSE, HTMLEL_TRCONTENTS }, { 5, "table", HTMLEL_BLOCK, HTMLEL_TABLECONTENTS }, { 2, "ul", HTMLEL_BLOCK, HTMLEL_LI }, { 0, 0, 0, 0 } }; #define HTMLELDESC_B (htmleldescs + 1) #define HTMLELDESC_BR (htmleldescs + 2) /*********************************************************************** * addcomment : add a comment to the list of comments if it has doxygen syntax * * Enter: tok struct */ void addcomment(struct tok *tok) { if (tok->len >= 1 && (tok->start[0] == '!' || (tok->type == TOK_BLOCKCOMMENT && tok->start[0] == '*') || (tok->type == TOK_INLINECOMMENT && tok->start[0] == '/'))) { struct comment *comment; comment = memalloc(sizeof(struct comment)); comment->text = memalloc(tok->len + 1); memcpy(comment->text, tok->start, tok->len); comment->text[tok->len] = 0; comment->type = tok->type; comment->filename = tok->filename; comment->linenum = tok->linenum; comment->node = 0; comment->back = 0; if (comment->text[1] == '<') { comment->back = 1; if (!lastidentifier) { locerrorexit(comment->filename, comment->linenum, "no identifier to attach doxygen comment to"); } comment->node = lastidentifier; } comment->next = comments; comments = comment; } } /*********************************************************************** * setcommentnode : set parse node to attach comments to * * Enter: node2 = parse node for identifier */ void setcommentnode(struct node *node2) { struct comment *comment = comments; while (comment && !comment->node) { comment->node = node2; comment = comment->next; } lastidentifier = node2; } /*********************************************************************** * joininlinecomments : join adjacent inline comments * * Enter: comment = list of comment structs * * Return: new list of comment structs * * This function also discards any single inline comment that does not * refer back. */ static struct comment * joininlinecomments(struct comment *comments) { struct comment **pcomment; pcomment = &comments; for (;;) { struct comment *comment; comment = *pcomment; if (!comment) break; if (comment->type != TOK_INLINECOMMENT) { /* Keep block comment as is. */ pcomment = &comment->next; } else if (!comment->back && (!comment->next || comment->next->type != TOK_INLINECOMMENT || comment->next->filename != comment->filename || comment->next->linenum != comment->linenum + 1)) { /* Discard single // comment that does not refer back. */ *pcomment = comment->next; } else { /* Find sequence of adjacent // comments (adjacent lines, * referring to same node) and join them. We do this in two * passes, one to count the length of the comment and one * to join. Note that the list is still in reverse order, * so we expect the line number to decrease by 1 each time. */ struct comment *newcomment = 0, *comment2; const char *filename = comment->filename; unsigned int linenum = comment->linenum; for (;;) { char *wp = newcomment->text; comment2 = comment; do { unsigned int len = strlen(comment2->text); if (newcomment) memcpy(wp, comment2->text, len); wp += len; linenum--; comment2 = comment2->next; } while (comment2 && comment2->filename == filename && comment2->linenum == linenum && comment2->node == comment->node); /* Finished a pass. */ if (newcomment) { *wp = 0; break; } newcomment = memalloc(sizeof(struct comment) + wp - newcomment->text); newcomment->node = comment->node; newcomment->type = comment->type; newcomment->filename = filename; newcomment->linenum = linenum + 1; } /* Replace the scanned comment struct with newcomment in the * list. */ newcomment->next = comment2; *pcomment = newcomment; pcomment = &newcomment->next; } } return comments; } /*********************************************************************** * outputchildren : call output recursively on children of cnode * * Enter: cnode * indent = indent (nesting) level of parent * indesc = whether already in or other top-level * descriptive element */ static void outputchildren(struct cnode *cnode, unsigned int indent, int indesc) { int curindesc = indesc; cnode = cnode->children; while (cnode) { if (curindesc != cnode->funcs->indesc) { assert(!indesc); printf("%*s<%sdescription>\n", indent + 1, "", curindesc ? "/" : ""); curindesc = !curindesc; } (*cnode->funcs->output)(cnode, indent + 2); cnode = cnode->next; } if (curindesc != indesc) printf("%*s<%sdescription>\n", indent + 1, "", curindesc ? "/" : ""); } /*********************************************************************** * default_askend : ask node if it wants to end at a para start (default * implementation) * * Enter: cnode * type = 0 else cnodefuncs for newly starting para * * Return: non-zero if this node wants to end */ static int default_askend(struct cnode *cnode, const struct cnodefuncs *type) { return 1; } /*********************************************************************** * root_askend : ask root node if it wants to end at a para start * * Enter: cnode for root * type = 0 else cnodefuncs for newly starting para * * Return: non-zero if this node wants to end */ static int root_askend(struct cnode *cnode, const struct cnodefuncs *type) { return 0; } /*********************************************************************** * root_output : output root cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void root_output(struct cnode *cnode, unsigned int indent) { outputchildren(cnode, indent, 0); } /*********************************************************************** * cnode type root */ static const struct cnodefuncs root_funcs = { 0, /* !indesc */ 1, /* needpara */ &root_askend, 0, /* end */ &root_output, }; /*********************************************************************** * endcnode : end current cnode * * Enter: cnode = current cnode * * Return: cnode = new current code (parent of old one) */ static struct cnode * endcnode(struct cnode *cnode) { if (cnode->funcs->end) (*cnode->funcs->end)(cnode); /* Reverse the children list. */ { struct cnode *child = cnode->children; cnode->children = 0; while (child) { struct cnode *next = child->next; child->next = cnode->children; cnode->children = child; child = next; } } return cnode->parent; } /*********************************************************************** * endspecificcnode : end a specific type of cnode * * Enter: cnode = current cnode * type = type of node to end * filename, linenum = filename and line number (for error reporting) * * Return: new current cnode */ static struct cnode * endspecificcnode(struct cnode *cnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum) { while (cnode->funcs != type) { if (cnode->funcs == &root_funcs) locerrorexit(filename, linenum, "unmatched \\endcode"); cnode = endcnode(cnode); } return cnode; } /*********************************************************************** * startcnode : start a newly created cnode * * Enter: cnode = current cnode * newcnode = new cnode to start * * Return: new current cnode (which is the same as newcnode) */ static struct cnode * startcnode(struct cnode *cnode, struct cnode *newcnode) { newcnode->parent = cnode; newcnode->next = cnode->children; cnode->children = newcnode; return newcnode; } /*********************************************************************** * para_output : output para cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void para_output(struct cnode *cnode, unsigned int indent) { printf("%*s

\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s

\n", indent, ""); } /*********************************************************************** * para_end : end a para cnode * * Enter: cnode struct */ static void para_end(struct cnode *cnode) { /* If the para cnode is empty, remove it. */ if (!cnode->children) cnode->parent->children = cnode->next; } /*********************************************************************** * cnode type para */ static const struct cnodefuncs para_funcs = { 1, /* indesc */ 0, /* !needpara */ &default_askend, ¶_end, /* end */ ¶_output, }; /*********************************************************************** * brief_output : output brief cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void brief_output(struct cnode *cnode, unsigned int indent) { printf("%*s\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type brief */ static const struct cnodefuncs brief_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &brief_output, }; /*********************************************************************** * return_output : output return cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void return_output(struct cnode *cnode, unsigned int indent) { printf("%*s

\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s

\n", indent, ""); } /*********************************************************************** * cnode type return */ static const struct cnodefuncs return_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &return_output, }; /*********************************************************************** * author_output : output name cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void name_output(struct cnode *cnode, unsigned int indent) { printf("%*s\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type name */ static const struct cnodefuncs name_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &name_output, }; /*********************************************************************** * author_output : output author cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void author_output(struct cnode *cnode, unsigned int indent) { printf("%*s\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type author */ static const struct cnodefuncs author_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &author_output, }; /*********************************************************************** * version_output : output version cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void version_output(struct cnode *cnode, unsigned int indent) { printf("%*s\n", indent, ""); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type version */ static const struct cnodefuncs version_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &version_output, }; /*********************************************************************** * cnode type code */ /*********************************************************************** * code_end : end a code cnode * * Enter: cnode struct */ static void code_end(struct cnode *cnode) { if (incode) { /* The incode flag has not been cleared, so this code cnode is * being ended implicitly. We complain about that. */ locerrorexit(cnode->filename, cnode->linenum, "mismatched \\code"); } } /*********************************************************************** * code_output : output code cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void code_output(struct cnode *cnode, unsigned int indent) { /* Note capitalization to differentiate it from HTML code element. */ if(cnode->attrtext) printf("%*s", indent, "", cnode->attrtext); else printf("%*s", indent, ""); outputchildren(cnode, indent, 1); printf("\n"); } static const struct cnodefuncs code_funcs = { 0, /* indesc */ 0, /* !needpara */ &default_askend, &code_end, /* end */ &code_output, }; /*********************************************************************** * startpara : start a new para cnode in the parse tree * * Enter: cnode = current cnode * type = vtable for particular type of cnode * * Return: new current cnode */ static struct cnode * startpara(struct cnode *cnode, const struct cnodefuncs *type) { struct cnode *newcnode; while ((*cnode->funcs->askend)(cnode, type)) cnode = endcnode(cnode); newcnode = memalloc(sizeof(struct cnode)); newcnode->funcs = type; return startcnode(cnode, newcnode); } /*********************************************************************** * cnode type text */ struct textcnode { struct cnode cn; unsigned char *data; unsigned int len; unsigned int max; }; /*********************************************************************** * text_end : end a text cnode * * Enter: cnode struct */ static void text_end(struct cnode *cnode) { struct textcnode *textcnode = (void *)cnode; textcnode->data[textcnode->len] = 0; textcnode->max = textcnode->len + 1; textcnode->data = memrealloc(textcnode->data, textcnode->max); } /*********************************************************************** * text_output : output text cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void text_output(struct cnode *cnode, unsigned int indent) { /* We do not indent, in case this is inside a code cnode. */ struct textcnode *textcnode = (void *)cnode; unsigned int len = textcnode->len; unsigned const char *p = textcnode->data; while (len) { unsigned int thislen; const char *thisptr; thislen = p[0]; /* (void *) cast is to avoid a warning from the MS compiler. * I think the warning is wrong, but I could be wrong. */ memcpy((void *)&thisptr, p + 1, sizeof(void *)); p += 1 + sizeof(void *); len -= 1 + sizeof(void *); printtext(thisptr, thislen, 0); } } static const struct cnodefuncs text_funcs = { 1, /* !indesc */ 0, /* !needpara */ &default_askend, &text_end, /* end */ &text_output, }; /*********************************************************************** * cnode type html (HTML element) */ struct htmlcnode { struct cnode cn; const struct htmleldesc *desc; char attrs[1]; }; /*********************************************************************** * html_end : end an html cnode * * Enter: cnode struct */ static void html_end(struct cnode *cnode) { if (((struct htmlcnode *)cnode)->desc->flags & HTMLEL_BLOCK) inhtmlblock--; } /*********************************************************************** * html_output : output html cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void html_output(struct cnode *cnode, unsigned int indent) { struct htmlcnode *htmlcnode = (void *)cnode; if (!(htmlcnode->desc->flags & HTMLEL_INLINE)) printf("%*s", indent, ""); if (htmlcnode->cn.children) { printf("<%s%s>", htmlcnode->desc->name, htmlcnode->attrs); if (!(htmlcnode->desc->flags & HTMLEL_INLINE)) putchar('\n'); outputchildren(&htmlcnode->cn, indent, 1); if (!(htmlcnode->desc->flags & HTMLEL_INLINE)) printf("%*s", indent, ""); printf("", htmlcnode->desc->name); } else printf("<%s%s/>", htmlcnode->desc->name, htmlcnode->attrs); if (!(htmlcnode->desc->flags & HTMLEL_INLINE)) putchar('\n'); } static const struct cnodefuncs html_funcs = { 1, /* indesc */ 0, /* !needpara */ &default_askend, &html_end, /* end */ &html_output, }; /*********************************************************************** * starthtmlcnode : start a new html cnode * * Enter: cnode = current cnode * htmleldesc = html element descriptor * attrs = attributes text * attrslen = length of attributes text * filename * linenum = line number * * Return: new current cnode */ static struct cnode * starthtmlcnode(struct cnode *cnode, const struct htmleldesc *htmleldesc, const char *attrs, unsigned int attrslen, const char *filename, unsigned int linenum) { struct htmlcnode *htmlcnode; /* First close enough elements to get to a content * model that will accept this new element. */ for (;;) { if (cnode->funcs != &html_funcs) { /* Not in any html element. We can accept any block element * (in which case we need to close the current paragraph * first) or any inline element (in which case we need to * close the current text cnode first). */ if (!(htmleldesc->flags & HTMLEL_INLINE)) { if (!(htmleldesc->flags & HTMLEL_BLOCK)) locerrorexit(filename, linenum, "<%s> not valid here", htmleldesc->name); while ((*cnode->funcs->askend)(cnode, 0)) cnode = endcnode(cnode); } else { while (cnode->funcs == &text_funcs) cnode = endcnode(cnode); } break; } htmlcnode = (struct htmlcnode *)cnode; if (!(htmleldesc->flags & htmlcnode->desc->content)) locerrorexit(filename, linenum, "<%s> not valid here", htmleldesc->name); break; } if (htmleldesc->flags & HTMLEL_BLOCK) inhtmlblock++; /* Create the new html cnode. */ htmlcnode = memalloc(sizeof(struct htmlcnode) + attrslen); htmlcnode->desc = htmleldesc; htmlcnode->cn.funcs = &html_funcs; htmlcnode->cn.filename = filename; htmlcnode->cn.linenum = linenum; memcpy(htmlcnode->attrs, attrs, attrslen); htmlcnode->attrs[attrslen] = 0; /* Start the html cnode. */ cnode = startcnode(cnode, &htmlcnode->cn); return cnode; } /*********************************************************************** * param_output : output param cnode * * Enter: cnode for param * indent = indent (nesting) level * * This is only used for a \param inside a \def-device-cap. A normal * \param that gets attached to a function argument gets changed to * a \return so it does not use this output function. */ static void param_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); outputchildren(¶mcnode->cn, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type param */ static const struct cnodefuncs param_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ ¶m_output, }; /*********************************************************************** * cnode type throw */ static const struct cnodefuncs throw_funcs = { 0, /* !indesc */ 0, /* !needpara */ &default_askend, 0, /* end */ &return_output, }; /*********************************************************************** * startparamcnode : start param (or throw) cnode * * Enter: cnode = current cnode * word = name of param * wordlen = length of name * inout = bit 0 = in, bit 1 = out * type = ¶m_funcs or &throw_funcs * * Return: new current cnode */ static struct cnode * startparamcnode(struct cnode *cnode, const char *word, unsigned int wordlen, int inout, const struct cnodefuncs *funcs) { struct paramcnode *paramcnode; paramcnode = memalloc(sizeof(struct paramcnode) + wordlen); paramcnode->cn.funcs = funcs; memcpy(paramcnode->name, word, wordlen); paramcnode->name[wordlen] = 0; paramcnode->inout = inout; return startcnode(cnode, ¶mcnode->cn); } /*********************************************************************** * api_feature_output : output api-feature cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void api_feature_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type api_feature */ static const struct cnodefuncs api_feature_funcs = { 0, /* !indesc */ 0, /* needpara */ &default_askend, 0, /* end */ &api_feature_output, }; /*********************************************************************** * device_cap_output : output device-cap cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void device_cap_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); outputchildren(cnode, indent, 1); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type device_cap */ static const struct cnodefuncs device_cap_funcs = { 0, /* !indesc */ 0, /* needpara */ &default_askend, 0, /* end */ &device_cap_output, }; /*********************************************************************** * def_api_feature_askend : ask if def-api-feature cnode wants to end at new para * * Enter: cnode for def-api-feature * type = cnodefuncs for new para (0 if html block element) * * Return: non-zero to end the def-api-feature */ static int def_api_feature_askend(struct cnode *cnode, const struct cnodefuncs *type) { /* A def-api-feature does not end at a plain para, an html block element, * a brief para, or a device-cap. */ if (!type || type == ¶_funcs || type == &device_cap_funcs || type == &brief_funcs) return 0; return 1; } /*********************************************************************** * def_api_feature_output : output def-api-feature cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void def_api_feature_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); printf("%*s\n", indent + 2, ""); outputchildren(cnode, indent + 2, 0); printf("%*s\n", indent + 2, ""); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type def_api_feature */ static const struct cnodefuncs def_api_feature_funcs = { 0, /* !indesc */ 1, /* needpara */ &def_api_feature_askend, 0, /* end */ &def_api_feature_output, }; /*********************************************************************** * def_api_feature_set_askend : ask if def-api-feature-set cnode wants to end at new para * * Enter: cnode for def-api-feature-set * type = cnodefuncs for new para (0 if html block element) * * Return: non-zero to end the def-api-feature-set */ static int def_api_feature_set_askend(struct cnode *cnode, const struct cnodefuncs *type) { /* A def-api-feature-set does not end at a plain para, an html block element, * a brief para, or an api-feature. */ if (!type || type == ¶_funcs || type == &api_feature_funcs || type == &brief_funcs) return 0; return 1; } /*********************************************************************** * def_api_feature_set_output : output def-api-feature-set cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void def_api_feature_set_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); printf("%*s\n", indent + 2, ""); outputchildren(cnode, indent + 2, 0); printf("%*s\n", indent + 2, ""); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type def_api_feature_set */ static const struct cnodefuncs def_api_feature_set_funcs = { 0, /* !indesc */ 1, /* needpara */ &def_api_feature_set_askend, 0, /* end */ &def_api_feature_set_output, }; /*********************************************************************** * def_instantiated_askend : ask if def-instantiated cnode wants to end at new para * * Enter: cnode for def-instantiated * type = cnodefuncs for new para (0 if html block element) * * Return: non-zero to end the def-instantiated */ static int def_instantiated_askend(struct cnode *cnode, const struct cnodefuncs *type) { /* A def-instantiated does not end at a plain para, an html block element, * a brief para, or an api-feature. */ if (!type || type == ¶_funcs || type == &api_feature_funcs || type == &brief_funcs) return 0; return 1; } /*********************************************************************** * def_instantiated_output : output def-instantiated cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void def_instantiated_output(struct cnode *cnode, unsigned int indent) { printf("%*s\n", indent, ""); printf("%*s\n", indent + 2, ""); outputchildren(cnode, indent + 2, 0); printf("%*s\n", indent + 2, ""); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type def_instantiated */ static const struct cnodefuncs def_instantiated_funcs = { 0, /* !indesc */ 1, /* needpara */ &def_instantiated_askend, 0, /* end */ &def_instantiated_output, }; /*********************************************************************** * def_device_cap_askend : ask if def-device-cap cnode wants to end at new para * * Enter: cnode for def-device-cap * type = cnodefuncs for new para (0 if html block element) * * Return: non-zero to end the def-device-cap */ static int def_device_cap_askend(struct cnode *cnode, const struct cnodefuncs *type) { /* A def-device-cap does not end at a plain para, an html block element, * a \brief para, or a param. */ if (!type || type == ¶_funcs || type == ¶m_funcs || type == &brief_funcs) return 0; return 1; } /*********************************************************************** * def_device_cap_output : output def_device-cap cnode * * Enter: cnode for root * indent = indent (nesting) level */ static void def_device_cap_output(struct cnode *cnode, unsigned int indent) { struct paramcnode *paramcnode = (void *)cnode; printf("%*s\n", indent, "", paramcnode->name); printf("%*s\n", indent + 2, ""); outputchildren(cnode, indent + 2, 0); printf("%*s\n", indent + 2, ""); printf("%*s\n", indent, ""); } /*********************************************************************** * cnode type def_device_cap */ static const struct cnodefuncs def_device_cap_funcs = { 0, /* !indesc */ 1, /* needpara */ &def_device_cap_askend, 0, /* end */ &def_device_cap_output, }; /*********************************************************************** * addtext : add text to current text node, starting one if necessary * * Enter: cnode = current cnode * text * len = length of text * * Return: new current cnode */ static struct cnode * addtext(struct cnode *cnode, const char *text, unsigned int len) { struct textcnode *textcnode; if (!len) return cnode; if (cnode->funcs != &text_funcs) { /* Start new text cnode. */ textcnode = memalloc(sizeof(struct textcnode)); textcnode->cn.funcs = &text_funcs; cnode = startcnode(cnode, &textcnode->cn); } textcnode = (void *)cnode; do { unsigned char buf[1 + sizeof(void *)]; unsigned int thislen = len; if (thislen > 255) thislen = 255; /* Encode a record as a single byte length followed by a pointer. */ buf[0] = thislen; memcpy(buf + 1, &text, sizeof(void *)); /* Add to the text cnode's data. */ if (textcnode->len + sizeof(buf) >= textcnode->max) { /* Need to reallocate (or allocate) data buffer. */ textcnode->max = textcnode->max ? 2 * textcnode->max : 1024; textcnode->data = memrealloc(textcnode->data, textcnode->max); } memcpy(textcnode->data + textcnode->len, buf, sizeof(buf)); textcnode->len += sizeof(buf); text += thislen; len -= thislen; } while (len); return &textcnode->cn; } /*********************************************************************** * iswhitespace : determine if character is whitespace * * Enter: ch = character * * Return: 1 if whitespace */ static inline int iswhitespace(int ch) { unsigned int i = ch - 1; if (i >= 32) return 0; return 0x80001100 >> i & 1; } /*********************************************************************** * parseword : parse the next word, ignoring leading whitespace * * Enter: *pp = text pointer * * Return: 0 else start of word (*pp updated to just beyond end) */ static const char * parseword(const char **pp) { const char *p = *pp, *word = 0; int ch = *p; while (iswhitespace(ch)) ch = *++p; word = p; while ((unsigned)((ch & ~0x20) - 'A') <= 'Z' - 'A' || (unsigned)(ch - '0') < 10 || ch == '_' || ch == '.' || ch == ':' || ch == '/' || ch == '-') { ch = *++p; } if (p == word) return 0; *pp = p; return word; } /*********************************************************************** * Doxygen command handlers * * Enter: p = text just after command * *pcnode = pointer to current cnode struct * type = 0 else cnodefuncs pointer for type of node to start * filename, linenum = current filename and line number (for error reporting) * * Return: p = updated if extra text was eaten * * On return, *pnode is updated if any node was closed or opened. */ /*********************************************************************** * Doxygen command handler : \b */ static const char * dox_b(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { struct cnode *cnode = *pcnode; const char *word = parseword(&p); /* Silently ignore \b with no following word. */ if (word) { struct cnode *mycnode; mycnode = cnode = starthtmlcnode(cnode, HTMLELDESC_B, 0, 0, filename, linenum); cnode = addtext(cnode, word, p - word); while (cnode != mycnode) cnode = endcnode(cnode); cnode = endcnode(cnode); } *pcnode = cnode; return p; } /*********************************************************************** * Doxygen command handler : \n */ static const char * dox_n(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { struct cnode *cnode = *pcnode; cnode = starthtmlcnode(cnode, HTMLELDESC_BR, 0, 0, filename, linenum); cnode = endcnode(cnode); *pcnode = cnode; return p; } /*********************************************************************** * Doxygen command handler : \code */ static const char * dox_code(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { *pcnode = startpara(*pcnode, &code_funcs); (*pcnode)->filename = filename; (*pcnode)->linenum = linenum; /* for reporting mismatched \code error */ incode = 1; return p; } /*********************************************************************** * Doxygen command handler : \endcode */ static const char * dox_endcode(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { incode = 0; *pcnode = endspecificcnode(*pcnode, &code_funcs, filename, linenum); return p; } /*********************************************************************** * Doxygen command handler : \param */ static const char * dox_param(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { struct cnode *cnode = *pcnode; unsigned int inout = 0; const char *word; /* Check for "in", "out" or both as attributes. */ if (*p == '[') { for (;;) { p++; if (!memcmp(p, "in", 2)) { inout |= 1; p += 2; } else if (!memcmp(p, "out", 3)) { inout |= 2; p += 3; } else break; if (*p != ',') break; } if (*p != ']') locerrorexit(filename, linenum, "bad attributes on \\param"); p++; } /* Get the next word as the parameter name. */ word = parseword(&p); if (!word) locerrorexit(filename, linenum, "expected word after \\param"); /* Close any open nodes. */ while ((*cnode->funcs->askend)(cnode, type)) cnode = endcnode(cnode); /* Create a new param cnode. */ cnode = startparamcnode(cnode, word, p - word, inout, type); cnode->filename = filename; cnode->linenum = linenum; *pcnode = cnode; return p; } /*********************************************************************** * Doxygen command handler : \brief, \return */ static const char * dox_para(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { *pcnode = startpara(*pcnode, type); return p; } /*********************************************************************** * Doxygen command handler : \throw */ static const char * dox_throw(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { struct cnode *cnode = *pcnode; const char *word; /* Get the next word as the exception name. */ word = parseword(&p); if (!word) locerrorexit(filename, linenum, "expected word after \\throw"); /* Close any open nodes. */ while ((*cnode->funcs->askend)(cnode, type)) cnode = endcnode(cnode); /* Create a new throw cnode. */ cnode = startparamcnode(cnode, word, p - word, 0, type); cnode->filename = filename; cnode->linenum = linenum; *pcnode = cnode; return p; } /*********************************************************************** * Doxygen command handler : \lang */ static const char * dox_attr(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname) { struct cnode *cnode = *pcnode; const char *word; int len, wordlen, offset = 0; char *attrtext; /* Get the next word as the attribute value. */ word = parseword(&p); if (!word) locerrorexit(filename, linenum, "expected word after \\%s", cmdname); len = strlen(cmdname) + (wordlen = p-word) + 4; /* p="word"\0 */ if(cnode->attrtext) len += (offset = strlen(cnode->attrtext)) + 1; /* add space for space */ attrtext = memalloc(len); if(offset) { memcpy(attrtext, cnode->attrtext, offset); attrtext[offset++] = ' '; memfree(((void*)cnode->attrtext)); } offset += sprintf(&attrtext[offset], "%s=\"", cmdname); memcpy(&attrtext[offset], word, wordlen); strcpy(&attrtext[offset + wordlen], "\""); cnode->attrtext = attrtext; /* skip delimiter because it won't be done otherwise */ if(incode && iswhitespace(*p)) ++p; return p; } /*********************************************************************** * commands : table of Doxygen commands */ struct command { const char *(*func)(const char *p, struct cnode **pcnode, const struct cnodefuncs *type, const char *filename, unsigned int linenum, const char *cmdname); const struct cnodefuncs *type; unsigned int namelen; const char *name; }; static const struct command commands[] = { { &dox_throw, &def_api_feature_funcs, 15, "def-api-feature" }, { &dox_throw, &def_api_feature_set_funcs, 19, "def-api-feature-set" }, { &dox_para, &def_instantiated_funcs, 16, "def-instantiated" }, { &dox_para, &name_funcs, 4, "name" }, { &dox_para, &author_funcs, 6, "author" }, { &dox_b, 0, 1, "b" }, { &dox_para, &brief_funcs, 5, "brief" }, { &dox_code, 0, 4, "code" }, { &dox_throw, &def_device_cap_funcs, 14, "def-device-cap" }, { &dox_attr, 0, 4, "lang" }, { &dox_endcode, 0, 7, "endcode" }, { &dox_n, 0, 1, "n" }, { &dox_param, ¶m_funcs, 5, "param" }, { &dox_para, &return_funcs, 6, "return" }, { &dox_throw, &throw_funcs, 5, "throw" }, { &dox_throw, &api_feature_funcs, 11, "api-feature" }, { &dox_throw, &device_cap_funcs, 10, "device-cap" }, { &dox_para, &version_funcs, 7, "version" }, { 0, 0, 0 } }; /*********************************************************************** * parsehtmltag : parse html tag * * Enter: start = start of tag, the '<' char * *pcnode = current cnode * filename = filename * *plinenum = current line number * * Return: just after the tag * *pcnode and *plinenum updated if applicable */ static const char * parsehtmltag(const char *start, struct cnode **pcnode, const char *filename, unsigned int *plinenum) { struct cnode *cnode = *pcnode; const char *end = start + 1, *endname = 0, *name = end; int ch = *end; int quote = 0; int close = 0; unsigned int linenum = *plinenum; const struct htmleldesc *htmleldesc; if (ch == '/') { close = 1; ch = *++end; name = end; } /* Find the end of the tag. */ for (;;) { if (!ch) locerrorexit(filename, *plinenum, "unterminated HTML tag"); if (ch == '\n') linenum++; else if (iswhitespace(ch) || ch == '/') { if (!endname) endname = end; } else if (!quote) { if (ch == '"' || ch == '\'') quote = ch; else if (ch == '>') break; } else { if (ch == quote) quote = 0; } ch = *++end; } if (!endname) endname = end; end++; /* See if it's an xml open-close tag. */ if (!close && endname != name && end[-2] == '/') close = 2; /* Find the tag from our list. */ htmleldesc = htmleldescs; for (;;) { if (!htmleldesc->namelen) { locerrorexit(filename, *plinenum, "unrecognized HTML tag %.*s", end - start, start); } if (htmleldesc->namelen == endname - name && !strncasecmp(htmleldesc->name, name, endname - name)) { break; } htmleldesc++; } if (close == 1) { /* Closing tag. Find open element to close. */ for (;;) { struct htmlcnode *htmlcnode; if (cnode->funcs != &text_funcs) { if (cnode->funcs != &html_funcs) { locerrorexit(filename, *plinenum, "mismatched %.*s", end - start, start); } htmlcnode = (struct htmlcnode *)cnode; if (htmlcnode->desc == htmleldesc) break; if (!(htmlcnode->desc->flags & HTMLEL_AUTOCLOSE)) { locerrorexit(filename, htmlcnode->cn.linenum, "mismatched <%.*s>", htmlcnode->desc->namelen, htmlcnode->desc->name); } } cnode = endcnode(cnode); } cnode = endcnode(cnode); } else { /* Opening tag. */ if (close !=2) cnode = starthtmlcnode(cnode, htmleldesc, endname, end - 1 - endname, filename, *plinenum); else // don't include the closing "/" in the attributes list cnode = starthtmlcnode(cnode, htmleldesc, endname, end - 2 - endname, filename, *plinenum); if (close == 2 || (htmleldesc->content & HTMLEL_EMPTY)) { /* Empty element -- close it again. */ cnode = endcnode(cnode); } } *pcnode = cnode; *plinenum = linenum; return end; } /*********************************************************************** * parsecomment : parse one comment * * Enter: comment struct */ static void parsecomment(struct comment *comment) { struct cnode *cnode = &comment->root; const char *p = comment->text + comment->back; unsigned int linenum = comment->linenum - 1; int ch; curcomment = comment; incode = 0; inhtmlblock = 0; cnode->funcs = &root_funcs; for (;;) { /* Start of new line. */ const char *starttext; ch = *p; linenum++; { /* Find first non-whitespace character. */ const char *p2 = p; int ch2 = ch; while (iswhitespace(ch2)) ch2 = *++p2; if (comment->type == TOK_BLOCKCOMMENT && ch2 == '*') { /* Ignore initial * in block comment (even in \code block). */ ch2 = *++p2; ch = ch2; p = p2; if (ch == '*') goto checkforlineofstars; while (iswhitespace(ch2)) ch2 = *++p2; } if (comment->type == TOK_INLINECOMMENT && ch2 == '/') { checkforlineofstars: if (!incode) { /* Ignore whole line of * for block comment or / for inline * comment if that is the only thing on the line. */ const char *p3 = p2; int ch3; do ch3 = *++p3; while (ch3 == ch2); while (iswhitespace(ch3)) ch3 = *++p3; if (!ch3 || ch3 == '\n') { /* Reached end of line (or whole comment) -- treat as * empty line. */ ch2 = ch3; p2 = p3; } } } if (!incode) { /* Only allow whitespace omission above to take effect if * not in \code block. */ ch = ch2; p = p2; } } if (!ch) { /* End of comments -- finish. */ break; } if (!incode && !inhtmlblock && ch == '\n') { /* Blank line -- finish any para, but only if not in code and * not in any HTML block element. */ while ((*cnode->funcs->askend)(cnode, 0)) cnode = endcnode(cnode); p++; continue; } /* Start new para if there isn't already one going. */ if (cnode->funcs->needpara) cnode = startpara(cnode, ¶_funcs); /* Process text on the line. */ starttext = p; while (ch && ch != '\n') { if (ch != '\\' && ch != '<' /* && ch != '@' */ && ch != '$' && ch != '&' && ch != '\r') { ch = *++p; continue; } /* Output any pending text. */ if (p - starttext) cnode = addtext(cnode, starttext, p - starttext); /* Ignore \r in DOS line returns */ if (ch == '\r') { ch = *++p; starttext = p; continue; } if (ch == '$') locerrorexit(comment->filename, linenum, "use \\$ instead of $"); /* See if it is an html named entity. */ if (ch == '&' && p[1] != '#') { const char *entity = ENTITIES; /* This search could be faster if the entity names were put * in a hash table or something. */ const char *semicolon = strchr(p, ';'); unsigned int len; if (!semicolon) locerrorexit(comment->filename, linenum, "unterminated HTML entity"); p++; for (;;) { len = strlen(entity); if (!len) locerrorexit(comment->filename, linenum, "unrecognised HTML entity &%.*s;", semicolon - p, p); if (len == semicolon - p && !memcmp(p, entity, len)) break; entity += len + 1; entity += strlen(entity) + 1; } entity += len + 1; cnode = addtext(cnode, entity, strlen(entity)); p = semicolon + 1; ch = *p; starttext = p; continue; } /* See if it is a backslash escape sequence. */ else if (ch == '\\') { const char *match = "\\@&$#<>%"; const char *pos; ch = p[1]; pos = strchr(match, ch); if (pos) { /* Got a \ escape sequence. */ const char *text = "\\\0 @\0 &\0$\0 #\0 <\0 >\0 %" + 6 * (pos - match); cnode = addtext(cnode, text, strlen(text)); p += 2; ch = *p; starttext = p; continue; } } else if (ch == '<') { if (incode) { ch = *++p; starttext = p; continue; } /* It's an html tag. */ p = parsehtmltag(p, &cnode, comment->filename, &linenum); ch = *p; starttext = p; continue; } { /* Got a doxygen command. First work out its length. */ const char *start = ++p; unsigned int cmdlen; const struct command *command; ch = *p; while ((unsigned)((ch & ~0x20) - 'A') <= 'Z' - 'A' || (unsigned)(ch - '0') < 10 || ch == '_' || ch == '-') { ch = *++p; } cmdlen = p - start; if (!cmdlen) locerrorexit(comment->filename, linenum, "\\ or @ without Doxygen command"); /* Look it up in the table. */ command = commands; for (;;) { if (!command->namelen) { locerrorexit(comment->filename, linenum, "unrecognized Doxygen command '%.*s'", cmdlen + 1, start - 1); } if (command->namelen == cmdlen && !memcmp(command->name, start, cmdlen)) { break; } command++; } p = (*command->func)(p, &cnode, command->type, comment->filename, linenum, command->name); ch = *p; starttext = p; } } if (p - starttext) { /* Start new para if there isn't already one going. */ if (cnode->funcs->needpara) cnode = startpara(cnode, ¶_funcs); cnode = addtext(cnode, starttext, p - starttext); } if (!ch) break; if (cnode->funcs == &text_funcs) addtext(cnode, "\n", 1); p++; } /* Finish the root cnode. */ do cnode = endcnode(cnode); while (cnode); assert(!incode); assert(!inhtmlblock); } /*********************************************************************** * parsecomments : parse comments * * Enter: comment = first comment in list */ static void parsecomments(struct comment *comment) { while (comment) { parsecomment(comment); comment = comment->next; } } /*********************************************************************** * attachcommenttonode : attach comment struct to node * * Enter: node = parse node for identifier * comment = comment struct */ static void attachcommenttonode(struct node *node, struct comment *comment) { comment->next = node->comments; node->comments = comment; } /*********************************************************************** * attachcomments : attach comments to applicable parse nodes * * Enter: comment = first in (reversed) list of comment structs * root = root parse node (for attaching \file comment blocks to) */ static void attachcomments(struct comment *comment, struct node *root) { while (comment) { struct comment *next = comment->next; /* See if there are any \param, \return, \throw, \def-api-feature or * \def-device-cap cnodes to detach and attach * elsewhere. (This only looks at top-level nodes, direct children * of the root, so does not detach a \param inside a * \def-device-cap.) */ struct cnode **pcnode = &comment->root.children; for (;;) { struct cnode *cnode = *pcnode; if (!cnode) break; if (cnode->funcs == ¶m_funcs || cnode->funcs == &return_funcs || cnode->funcs == &throw_funcs) { /* Found a \param or \return or \throw to detach. Find the * parameter/exception of the same name, or the return type. */ struct node *node; struct comment *newcomment; if (cnode->funcs == ¶m_funcs) { node = findparamidentifier(comment->node, ((struct paramcnode *)cnode)->name); if (!node) locerrorexit(comment->filename, cnode->linenum, "no parameter '%s' found", ((struct paramcnode *)cnode)->name); } else if (cnode->funcs == &return_funcs) { node = findreturntype(comment->node); if (!node) locerrorexit(comment->filename, cnode->linenum, "no return type found"); } else { node = findthrowidentifier(comment->node, ((struct paramcnode *)cnode)->name); if (!node) locerrorexit(comment->filename, cnode->linenum, "no exception '%s' found", ((struct paramcnode *)cnode)->name); } /* Detach the cnode from its old comment. */ *pcnode = cnode->next; /* Create a new comment struct to contain this cnode. */ newcomment = memalloc(sizeof(struct comment)); newcomment->root.funcs = &root_funcs; newcomment->linenum = cnode->linenum; /* Attach the cnode. */ newcomment->root.children = cnode; cnode->parent = &newcomment->root; cnode->next = 0; /* Make the cnode a \return one, just so even a \param * uses return_output. */ cnode->funcs = &return_funcs; /* Attach the new comment struct to the parse node. */ attachcommenttonode(node, newcomment); } else { pcnode = &cnode->next; } } /* Now attach the comment to its identifier parse node. */ { struct node *node = comment->node; if (!node) node = root; attachcommenttonode(node, comment); } comment = next; } } /*********************************************************************** * processcomments : join, parse and attach comments * * Enter: root = root parse node */ void processcomments(struct node *root) { comments = joininlinecomments(comments); parsecomments(comments); attachcomments(comments, root); } /*********************************************************************** * outputdescriptive : output descriptive elements for a node * * Enter: node = identifier node that might have some comments * indent = indent (nesting) level */ void outputdescriptive(struct node *node, unsigned int indent) { struct comment *comment = node->comments; int indescriptive = 0; while (comment) { struct cnode *root = &comment->root; if (!indescriptive) printf("%*s\n", indent, ""); indescriptive = 1; (*root->funcs->output)(root, indent + 2); comment = comment->next; } if (indescriptive) printf("%*s\n", indent, ""); }