| /*********************************************************************** |
| * $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 <assert.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #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 <description> */ |
| 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 <description> 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<p>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</p>\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<brief>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</brief>\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<description><p>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</p></description>\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<name>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</name>\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<author>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</author>\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<version>\n", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</version>\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<Code %s>", indent, "", cnode->attrtext); |
| else |
| printf("%*s<Code>", indent, ""); |
| outputchildren(cnode, indent, 1); |
| printf("</Code>\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("</%s>", 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<param identifier=\"%s\">\n", indent, "", paramcnode->name); |
| outputchildren(¶mcnode->cn, indent, 1); |
| printf("%*s</param>\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<api-feature identifier=\"%s\">\n", indent, "", paramcnode->name); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</api-feature>\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<device-cap identifier=\"%s\">\n", indent, "", paramcnode->name); |
| outputchildren(cnode, indent, 1); |
| printf("%*s</device-cap>\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<def-api-feature identifier=\"%s\">\n", indent, "", paramcnode->name); |
| printf("%*s<descriptive>\n", indent + 2, ""); |
| outputchildren(cnode, indent + 2, 0); |
| printf("%*s</descriptive>\n", indent + 2, ""); |
| printf("%*s</def-api-feature>\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<def-api-feature-set identifier=\"%s\">\n", indent, "", paramcnode->name); |
| printf("%*s<descriptive>\n", indent + 2, ""); |
| outputchildren(cnode, indent + 2, 0); |
| printf("%*s</descriptive>\n", indent + 2, ""); |
| printf("%*s</def-api-feature-set>\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<def-instantiated>\n", indent, ""); |
| printf("%*s<descriptive>\n", indent + 2, ""); |
| outputchildren(cnode, indent + 2, 0); |
| printf("%*s</descriptive>\n", indent + 2, ""); |
| printf("%*s</def-instantiated>\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<def-device-cap identifier=\"%s\">\n", indent, "", paramcnode->name); |
| printf("%*s<descriptive>\n", indent + 2, ""); |
| outputchildren(cnode, indent + 2, 0); |
| printf("%*s</descriptive>\n", indent + 2, ""); |
| printf("%*s</def-device-cap>\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<descriptive>\n", indent, ""); |
| indescriptive = 1; |
| (*root->funcs->output)(root, indent + 2); |
| comment = comment->next; |
| } |
| if (indescriptive) |
| printf("%*s</descriptive>\n", indent, ""); |
| } |