ADDED   src/creoleparser.c
Index: src/creoleparser.c
==================================================================
--- src/creoleparser.c
+++ src/creoleparser.c
@@ -0,0 +1,1163 @@
+/*
+** Copyright (c) 2009 Robert Ledger
+**
+** {{{ License
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License version 2 as published by the Free Software Foundation.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   robert@pytrash.co.uk
+**   http://pytrash.co.uk
+**}}}
+*******************************************************************************
+**
+** This file contains code to render creole 1.0 formated text as html.
+*/
+#include <assert.h>
+#include "config.h"
+#include "creoleparser.h"
+
+#if INTERFACE
+#define HAVE_CREOLE_MACRO 1
+#endif
+
+//{{{ LOCAL INTERFACE
+#if LOCAL_INTERFACE
+
+#define POOL_CHUNK_SIZE 100
+
+//{{{ KIND
+#define KIND_ROOT            0x0000001
+#define KIND_HORIZONTAL_RULE 0x0000002
+#define KIND_HEADING         0x0000004
+#define KIND_ORDERED_LIST    0x0000008
+
+#define KIND_UNORDERED_LIST  0x0000010
+#define KIND_PARAGRAPH       0x0000020
+#define KIND_TABLE           0x0000040
+#define KIND_NO_WIKI_BLOCK   0x0000080
+
+#define KIND_PARA_BREAK      0x0000100
+#define KIND_END_WIKI_MARKER 0x0000200
+
+#define KIND_BOLD            0x0000400
+#define KIND_ITALIC          0x0000800
+#define KIND_SUPERSCRIPT     0x0001000
+#define KIND_SUBSCRIPT       0x0002000
+#define KIND_MONOSPACED      0x0004000
+#define KIND_BREAK           0x0008000
+
+#define KIND_TABLE_ROW       0x0010000
+#define KIND_MACRO           0X0020000
+//}}}
+//{{{ MACRO
+#define MACRO_NONE           0X0000000
+#define MACRO_FOSSIL         0x0000001
+#define MACRO_WIKI_CONTENTS  0X0000002
+//}}}
+//{{{ FLAG
+// keep first four bits free (why?:)
+#define FLAG_CENTER      0x0000100
+#define FLAG_MACRO_BLOCK 0X0000200
+//}}}
+struct Node {//{{{
+
+  char *start;
+  char *end;
+
+  int kind;
+  int level;
+  int flags;
+
+  Node *parent;
+  Node *next;
+  Node *children;
+
+};
+//}}}
+struct NodePool {//{{{
+  NodePool *next;
+  Node a[POOL_CHUNK_SIZE];
+}
+//}}}
+struct Parser {//{{{
+
+  Blob *pOut;                 /* Output appended to this blob */
+  Renderer *r;
+
+  NodePool *pool;
+  int nFree;
+
+  Node *this;
+  Node *previous;
+  Node *list;
+
+  char *cursor;
+
+  int lineWasBlank;
+  int charCount;
+
+  Node *item;
+  Node *istack;
+  char *icursor;
+  char *iend;
+
+  int inLink;
+  int inTable;
+  int iesc;
+
+  Blob *iblob;
+
+};
+//}}}
+
+#endif
+
+const int KIND_LIST = (KIND_UNORDERED_LIST | KIND_ORDERED_LIST);
+const int KIND_LIST_OR_PARAGRAPH = (KIND_PARAGRAPH | KIND_UNORDERED_LIST | KIND_ORDERED_LIST);
+//}}}
+
+//{{{ POOL MANAGEMENT
+static Node *pool_new(Parser *p){
+
+  if ( p->pool == NULL || p->nFree == 0){
+
+    NodePool *temp = p->pool;
+
+    p->pool = malloc(sizeof(NodePool));
+    if( p->pool == NULL ) fossil_panic("out of memory");
+
+    p->pool->next = temp;
+    p->nFree = POOL_CHUNK_SIZE;
+  }
+  p->nFree -= 1;
+  Node *node = &(p->pool->a[p->nFree]);
+  memset(node, 0, sizeof(*node));
+
+  return node;
+}
+
+
+static void pool_free(Parser *p){
+
+  NodePool *temp;
+
+  while (p->pool != NULL){
+    temp = p->pool;
+    p->pool = temp->next;
+    free(temp);
+  }
+
+}
+//}}}
+
+//{{{ Utility Methods
+
+static char *cr_skipBlanks(Parser *p, char* z){//{{{
+  char *s = z;
+  while (z[0] == ' ' || z[0] == '\t') z++;
+  p->charCount = z - s;
+  return z;
+}
+//}}}
+static int cr_countBlanks(Parser *p, char* z){//{{{
+  cr_skipBlanks(p, z);
+  return p->charCount;
+}
+//}}}
+static char *cr_skipChars(Parser *p, char *z, char c){//{{{
+  char *s = z;
+  while (z[0] == c) z++;
+  p->charCount = z - s;
+  return z;
+}
+//}}}
+static int cr_countChars(Parser *p, char *z, char c){//{{{
+  cr_skipChars(p, z, c);
+  return p->charCount;
+}
+//}}}
+static char *cr_nextLine(Parser *p, char *z){//{{{
+
+  p->lineWasBlank = 1;
+
+  while (1){
+
+    switch (z[0]){
+
+      case '\r':
+        if (z[1] == '\n') {
+          z[0] = ' ';
+          return z + 2;
+        }
+        z[0] = '\n';
+        return z + 1;
+
+      case '\n':
+        return z + 1;
+
+      case '\t':
+        z[0] = ' ';
+        z++;
+        break;
+
+      case ' ':
+        z++;
+        break;
+
+      case '\0':
+        return z;
+
+      default:
+        p->lineWasBlank = 0;
+        z++;
+    }
+  }
+}
+//}}}
+//}}}
+
+
+//{{{ INLINE PARSER
+
+static int cr_isEsc(Parser *p){//{{{
+  if (p->iesc){
+    blob_append(p->iblob, p->icursor, 1);
+    p->iesc = 0;
+    p->icursor += 1;
+    return 1;
+  }
+  return 0;
+}
+//}}}
+static int cr_iOpen(Parser *p, int kind){//{{{
+
+  switch (kind){
+
+    case KIND_BOLD:
+      blob_append(p->iblob, "<strong>", 8);
+      return 1;
+
+    case KIND_ITALIC:
+      blob_append(p->iblob, "<em>", 4);
+      return 1;
+
+    case KIND_SUPERSCRIPT:
+      blob_append(p->iblob, "<sup>", 5);
+      return 1;
+
+    case KIND_SUBSCRIPT:
+      blob_append(p->iblob, "<sub>", 5);
+      return 1;
+
+    case KIND_MONOSPACED:
+      blob_append(p->iblob, "<tt>", 4);
+      return 1;
+  }
+  return 0;
+}
+//}}}
+static int cr_iClose(Parser *p, int kind){//{{{
+
+  switch (kind){
+
+    case KIND_BOLD:
+      blob_append(p->iblob, "</strong>", 9);
+      return 1;
+
+    case KIND_ITALIC:
+      blob_append(p->iblob, "</em>", 5);
+      return 1;
+
+    case KIND_SUPERSCRIPT:
+      blob_append(p->iblob, "</sup>", 6);
+      return 1;
+
+    case KIND_SUBSCRIPT:
+      blob_append(p->iblob, "</sub>", 6);
+      return 1;
+
+    case KIND_MONOSPACED:
+      blob_append(p->iblob, "</tt>", 5);
+      return 1;
+  }
+  return 0;
+}
+//}}}
+
+
+static void cr_iMarkup(Parser *p, int kind){//{{{
+
+  if (p->iesc) {
+    blob_append(p->iblob, p->icursor, 1);
+    p->icursor +=1;
+    p->iesc =0;
+    return;
+  }
+
+  if (p->icursor[1] != p->icursor[0]) {
+    blob_append(p->iblob, p->icursor, 1);
+    p->icursor +=1;
+    return;
+  }
+
+  p->icursor += 2;
+
+  if (kind & KIND_BREAK) {
+      blob_append(p->iblob, "<br />", 6);
+      return;
+  }
+
+  if (kind & KIND_ITALIC && p->icursor[-3] == ':'){
+        blob_append(p->iblob, "//", 2);
+        return;
+  }
+
+  Node *n = p->istack;
+
+  int found = 0;
+  while (n) {
+    if (n->kind & kind) {
+      found = 1;
+      break;
+    }
+    n = n->next;
+  }
+
+  if (!found) {
+    n = pool_new(p);
+    n->kind = kind;
+    n->next = p->istack;
+    p->istack = n;
+
+    assert(cr_iOpen(p, kind));
+    return;
+  };
+
+  n= p->istack;
+  while (n){
+    p->istack = n->next;
+
+    assert(cr_iClose(p, n->kind));
+
+    if (kind == n->kind) return;
+    n = p->istack;
+  }
+}
+//}}}
+static int cr_iNoWiki(Parser *p){//{{{
+
+  if ((p->iend - p->icursor)<6) return 0;
+
+  if (p->icursor[1]!='{' || p->icursor[2]!='{')
+    return 0;
+
+  char *s = p->icursor + 3;
+
+  int count = p->iend - p->icursor - 3;
+  while (count--){
+    if (s[0]=='}' && s[1]=='}' && s[2]=='}' && s[3]!='}'){
+      blob_appendf(p->iblob, "<tt class='creole-inline-nowiki'>%s</tt>", htmlize(p->icursor + 3, s - p->icursor-3));
+      p->icursor = s + 3;
+      return 1;
+    }
+    s++;
+  }
+  return 0;
+}
+
+//}}}
+static int cr_iImage(Parser *p){//{{{
+
+  if (p->inLink) return 0;
+  if ((p->iend - p->icursor)<3) return 0;
+
+  if (p->icursor[1]!='{') return 0;
+
+  char *s = p->icursor + 2;
+  char *bar = NULL;
+
+  int count = p->iend - p->icursor - 4;
+  while (count--){
+    if (s[0]=='}' && s[1]=='}'){
+      if (!bar) bar = p->icursor + 2;
+      blob_appendf(p->iblob, "<span class='creole-noimage'>%s</span>", htmlize(bar, s - bar ));
+      p->icursor = s + 2;
+      return 1;
+    }
+    if (!bar && s[0]=='|') bar=s+1;
+    s++;
+  }
+  return 0;
+}
+//}}}
+static int cr_iMacro(Parser *p){//{{{
+
+  if (p->inLink) return 0;
+  if ((p->iend - p->icursor)<3) return 0;
+
+  if (p->icursor[1]!='<') return 0;
+
+  char *s = p->icursor + 2;
+
+  int count = p->iend - p->icursor - 3;
+  while (count--){
+   if (s[0]=='>' && s[1]=='>'){
+      blob_appendf(p->iblob, "<span class='creole-nomacro'>%s</span>", htmlize(p->icursor, s - p->icursor + 2));
+      p->icursor = s + 2;
+      return 1;
+    }
+    s++;
+  }
+  return 0;
+
+}
+//}}}
+
+static void cr_renderLink(Parser *p, char *s, char *bar, char *e){//{{{
+
+  int tsize = bar-s;
+  int dsize = e - bar-1;
+
+  if (tsize < 1) return;
+  if (dsize < 1) dsize = 0;
+
+  char zTarget[tsize + 1];
+  memcpy(zTarget, s, tsize);
+  zTarget[tsize] = '\0';
+
+  char zClose[20];
+
+  Blob *pOut = p->r->pOut;
+
+  p->r->pOut = p->iblob;
+  wf_openHyperlink(p->r, zTarget, zClose, sizeof(zClose));
+  p->r->pOut = pOut;
+
+  if (dsize)
+    cr_parseInline(p, bar+1, e) ;
+  else
+    blob_append(p->iblob, htmlize(s, tsize), -1);
+  blob_append(p->iblob, zClose, -1);
+}
+//}}}
+
+static int cr_iLink(Parser *p){//{{{
+
+  if (p->inLink) return 0;
+  if ((p->iend - p->icursor)<3) return 0;
+
+  if (p->icursor[1]!='[') return 0;
+
+  char *s = p->icursor + 2;
+  char *bar = NULL;
+
+  int count = p->iend - p->icursor -3;
+  while (count--){
+    if (s[0]==']' && s[1]==']'){
+      if (!bar) bar = s;
+      p->inLink = 1;
+      cr_renderLink(p, p->icursor+2, bar, s);
+      p->inLink = 0;
+      p->icursor = s + 2;
+      return 1;
+    }
+    if (!bar && s[0]=='|') bar=s;
+    s++;
+  }
+  return 0;
+}
+//}}}
+
+LOCAL char *cr_parseInline(Parser *p, char *s, char *e){//{{{
+
+  int save_iesc = p->iesc;
+  char *save_iend = p->iend;
+  Node *save_istack = p->istack;
+
+  p->iesc = 0;
+  p->iend = e;
+  p->istack = NULL;
+
+  p->icursor = s;
+
+  char *eof = NULL;
+  while (!eof &&  p->icursor < p->iend ){
+
+    switch (*p->icursor) {//{{{
+
+      case '~':
+        if (p->iesc) {
+          blob_append(p->iblob, "~", 1);
+          p->iesc = 0;
+        }
+        p->iesc = !p->iesc;
+        p->icursor+=1;
+        break;
+
+      case '*':
+        cr_iMarkup(p, KIND_BOLD);
+        break;
+
+      case '/':
+        cr_iMarkup(p, KIND_ITALIC);
+        break;
+
+      case '^':
+        cr_iMarkup(p, KIND_SUPERSCRIPT);
+        break;
+
+      case ',':
+        cr_iMarkup(p, KIND_SUBSCRIPT);
+        break;
+
+      case '#':
+        cr_iMarkup(p, KIND_MONOSPACED);
+        break;
+
+      case '\\':
+        cr_iMarkup(p, KIND_BREAK);
+        break;
+
+      case '{':
+        if (cr_isEsc(p)) break;
+        if (cr_iNoWiki(p)) break;
+        if (cr_iImage(p)) break;
+        blob_append(p->iblob, p->icursor, 1);
+        p->icursor += 1;
+        break;
+
+      case '[':
+        if (cr_isEsc(p)) break;
+        if (cr_iLink(p)) break;
+        blob_append(p->iblob, p->icursor, 1);
+        p->icursor += 1;
+        break;
+
+
+      case '<':
+        if (cr_isEsc(p)) break;
+        if (cr_iMacro(p)) break;
+
+        blob_append(p->iblob, "&lt;", 4);
+        p->icursor += 1;
+        break;
+
+      case '>':
+        if (p->iesc) {
+          blob_append(p->iblob, "~", 1);
+          p->iesc = 0;
+        }
+        blob_append(p->iblob, "&gt;", 4);
+        p->icursor += 1;
+        break;
+
+      case '&':
+        if (p->iesc) {
+          blob_append(p->iblob, "~", 1);
+          p->iesc = 0;
+        }
+        blob_append(p->iblob, "&amp;", 5);
+        p->icursor += 1;
+        break;
+
+      case '|':
+        if (p->inTable){
+          if (p->iesc) {
+            blob_append(p->iblob, p->icursor, 1);
+            p->iesc = 0;
+            p->icursor += 1;
+            break;
+          }
+          eof = p->icursor + 1;
+          break;
+        }
+        // fall through to default
+
+      default:
+        if (p->iesc) {
+          blob_append(p->iblob, "~", 1);
+          p->iesc = 0;
+        }
+        blob_append(p->iblob, p->icursor, 1);
+        p->icursor +=1;
+    }//}}}
+
+  }
+
+  while (p->istack){
+    cr_iClose(p, p->istack->kind);
+    p->istack = p->istack->next;
+  }
+
+  p->iesc = save_iesc;
+  p->iend = save_iend;
+  p->istack = save_istack;
+
+  return eof;
+
+}
+//}}}
+//}}}
+
+//{{{ BLOCK PARSER
+
+static void cr_renderListItem(Parser *p, Node *n){//{{{
+
+
+  blob_append(p->iblob, "<li>", 4);
+  cr_parseInline(p, n->start, n->end);
+
+  if (n->children){
+
+    int ord = (n->children->kind & KIND_ORDERED_LIST);
+
+    if (ord)   blob_append(p->iblob, "<ol>", 4);
+    else       blob_append(p->iblob, "<ul>", 4);
+
+    n = n->children;
+    while (n){
+      cr_renderListItem(p, n);
+      n = n->next;
+    }
+
+    if (ord)   blob_append(p->iblob, "</ol>", 5);
+    else       blob_append(p->iblob, "</ul>", 5);
+  }
+  blob_append(p->iblob, "</li>", 5);
+}
+//}}}
+static void cr_renderList(Parser *p){//{{{
+
+  Node *n = p->list;
+
+  while (n->parent !=n)  n = n->parent;
+
+  int ord = (n->kind & KIND_ORDERED_LIST);
+
+  if (ord)   blob_append(p->iblob, "\n\n<ol>", -1);
+  else       blob_append(p->iblob, "\n\n<ul>", -1);
+
+  while (n) {
+    cr_renderListItem(p, n);
+    n = n->next;
+  }
+
+  if (ord)   blob_append(p->iblob, "</ol>", 5);
+  else       blob_append(p->iblob, "</ul>", 5);
+}
+
+//}}}
+
+static void cr_renderTableRow(Parser *p, Node *row){//{{{
+
+  char *s = row->start;
+  int th;
+
+  blob_append(p->iblob, "\n<tr>", -1);
+
+  while (s && s < row->end){
+
+    if ((th = *s == '=')) {
+      s++;
+      blob_append(p->iblob, "<th>", -1);
+    }
+    else {
+      blob_append(p->iblob, "<td>", -1);
+    }
+
+    s = cr_parseInline(p, s, row->end);
+
+    if (th)
+      blob_append(p->iblob, "</th>\n", -1);
+    else
+      blob_append(p->iblob, "</td>\n", -1);
+
+    if (!s) break;
+  }
+  blob_append(p->iblob, "</tr>", 5);
+}
+//}}}
+static void cr_renderTable(Parser *p, Node *n){//{{{
+
+  Node *row = n->children;
+
+  blob_append(p->iblob, "<table class='creoletable'>", -1);
+  p->inTable = 1;
+  while (row){
+
+    cr_renderTableRow(p, row);
+    row = row->next;
+
+  }
+  blob_append(p->iblob, "</table>", -1);
+  p->inTable = 0;
+
+}
+//}}}
+
+static void cr_renderMacro(Parser *p, Node *n){//{{{
+
+  switch (n->level){
+
+    case MACRO_WIKI_CONTENTS:
+      do_macro_wiki_contents(p, n);
+      break;
+
+  }
+
+}
+//}}}
+
+static void cr_render(Parser *p, Node *node){//{{{
+
+  if (node->kind & KIND_PARAGRAPH){
+    blob_append(p->iblob,   "\n<p>", -1);
+    cr_parseInline(p, node->start, node->end );
+    blob_append(p->iblob, "</p>\n", -1  );
+  }
+
+  if (node->kind & KIND_HEADING){
+    blob_appendf(p->iblob,
+        "\n<h%d %s>",
+        node->level,
+        (node->flags & FLAG_CENTER) ? " style='text-align:center;'" : ""
+    );
+    cr_parseInline(p, node->start, node->end);
+    blob_appendf(p->iblob, "</h%d>\n", node->level  );
+    return;
+  }
+
+  if (node->kind & KIND_MACRO){
+    cr_renderMacro(p, node);
+    return;
+  }
+
+  if (node->kind & KIND_HORIZONTAL_RULE){
+    blob_append(p->iblob, "<hr />", -1);
+    return;
+  }
+
+  if (node->kind & KIND_LIST){
+    cr_renderList(p);
+    p->list = NULL;
+    return;
+  }
+
+  if (node->kind & KIND_TABLE){
+    cr_renderTable(p, node);
+    return;
+  }
+
+  if (node->kind & KIND_NO_WIKI_BLOCK){
+    blob_appendf(p->iblob,
+      "\n<pre class='creole-block-nowiki'>%s</pre>\n",
+        htmlize( node->start, node->end - node->start)
+    );
+  }
+}
+//}}}
+
+static char *cr_findEndOfBlock(Parser *p, char *s, char c){//{{{
+
+  char *end;
+  while (s[0]){
+
+    end = s;
+    if (s[0] == c && s[0] == c && s[0] == c) {
+      s = cr_nextLine(p, s + 3);
+      if (p->lineWasBlank) {
+          p->cursor = s;
+          return end;
+      }
+    }
+    else {
+      s = cr_nextLine(p, s);
+    }
+  }
+  return 0;
+}
+//}}}
+static int cr_addListItem(Parser *p, Node *n){//{{{
+
+  n->parent = n;
+  n->next = n->children = NULL;
+
+  if (!p->list) {
+    if (n->level != 1) return 0;
+    p->list = n;
+    return 1;
+  }
+
+  Node *list = p->list;
+
+  while (n->level < list->level){
+    list = list->parent;
+  }
+
+  if (n->level == list->level){
+
+    if (n->kind != list->kind){
+      if (n->level>1) return 0;
+      cr_renderList(p);
+      p->list = n;
+      return 1;
+    }
+    n->parent = list->parent;
+    p->list = list->next = n;
+    return 1;
+  }
+
+  if ( (n->level - list->level) > 1 ) return 0;
+  n->parent = p->list;
+  p->list->children = n;
+  p->list = n;
+  return 1;
+
+}
+//}}}
+
+static int isEndWikiMarker(Parser *p){//{{{
+
+  char *s = p->cursor;
+  if (memcmp(s, "<<fossil>>", 10)) return 0;
+  p->this->start = s;
+  p->this->kind = KIND_END_WIKI_MARKER;
+  p->cursor += 10;
+  return 1;
+}
+//}}}
+static int isNoWikiBlock(Parser *p){//{{{
+
+  char *s = p->cursor;
+
+  if (s[0] != '{') return 0; s++;
+  if (s[0] != '{') return 0; s++;
+  if (s[0] != '{') return 0; s++;
+
+  s = cr_nextLine(p, s);
+  if (!p->lineWasBlank) return 0;
+
+  p->this->start = s;
+
+  s = cr_findEndOfBlock(p, s, '}');
+
+  if (!s) return 0;
+
+  // p->cursor was set by findEndOfBlock
+  p->this->kind = KIND_NO_WIKI_BLOCK;
+  p->this->end = s;
+  return 1;
+}
+
+//}}}
+static int isParaBreak(Parser *p){//{{{
+
+  char *s = cr_nextLine(p, p->cursor);
+  if (!p->lineWasBlank) return 0;
+
+  p->cursor = s;
+  p->this->kind = KIND_PARA_BREAK;
+  return 1;
+}
+//}}}
+static int isMacro(Parser *p){//{{{
+
+  char *s = p->cursor;
+  int macroId;
+  int matchLength;
+
+  /* This is succinct but somewhat hard to follow.
+     Read as: If not '<' then return, otherwise increment s;
+              Repeat above;
+              If '<' then return;
+  */
+  if (s[0]!='<') return 0; s++;
+  if (s[0]!='<') return 0; s++;
+  if (s[0]=='<') return 0;
+
+  matchLength = cr_has_macro(s, &macroId);
+  if (!matchLength) return 0;
+
+  s += matchLength;
+  p->this->start = s;
+
+  if (s[-1]!='>'){
+    while (s[0] && s[1] && s[0]!='\n' && !(s[0]=='>' && s[1]=='>')) s++;
+    if (!(s[0] == '>' && s[1] == '>')) return 0;
+    s +=2;
+  }
+  p->cursor = s;
+  p->this->kind = KIND_MACRO;
+  p->this->level = macroId;
+  p->this->flags &= FLAG_MACRO_BLOCK;
+  p->this->end = s-2;
+  return 1;
+}
+//}}}
+static int isHeading(Parser *p){//{{{
+
+  char *s = cr_skipBlanks(p, p->cursor);
+
+  int flags = 0;
+  int level = cr_countChars(p, s, '=');
+  if (!level) return 0;
+
+  s += level;
+
+  if (s[0] == '<' && s[1] == '>') {
+    flags |= FLAG_CENTER;
+    s += 2;
+  }
+  s = cr_skipBlanks(p, s);
+
+  p->this->start = s;
+
+  s = cr_nextLine(p, s);
+  char *z = s;
+
+  if (s[-1] == '\n') s--;
+  while(s[-1] == ' ' || s[-1]=='\t') s--;
+  while(s[-1] == '=' ) s--;
+  if (p->this->start < s){
+    p->cursor = z;
+    p->this->kind = KIND_HEADING;
+    p->this->end = s;
+    p->this->level = level;
+    p->this->flags |= flags;
+    return 1;
+  }
+  return 0;
+}
+//}}}
+static int isHorizontalRule(Parser *p){//{{{
+
+  char *s = cr_skipBlanks(p, p->cursor);
+
+  int level = cr_countChars(p, s, '-');
+
+  if  (level < 4) return 0;
+  s = cr_nextLine(p, s + level);
+  if (!p->lineWasBlank) return 0;
+
+  p->cursor = s;
+  p->this->kind = KIND_HORIZONTAL_RULE;
+
+  return 1;
+}
+//}}}
+static int isListItem(Parser *p){//{{{
+
+  char *s = cr_skipBlanks(p, p->cursor);
+
+  int level = cr_countChars(p, s, '#');
+  if (!level) level = cr_countChars(p, s, '*');
+
+  if ( !level) return 0;
+
+  p->this->kind = (s[0] == '#') ? KIND_ORDERED_LIST : KIND_UNORDERED_LIST;
+  p->this->level = level;
+
+  s = cr_skipBlanks(p, s + level);
+  p->this->start = s;
+
+  s = cr_nextLine(p, s);
+  if (p->lineWasBlank) return 0;
+
+  if (cr_addListItem(p, p->this)){
+    p->cursor = p->this->end = s;
+    return 1;
+  }
+  p->this->kind = 0;
+  return 0;
+}
+//}}}
+static int isTable(Parser *p){//{{{
+
+  p->this->start = p->cursor;
+  char *s = cr_skipBlanks(p, p->cursor);
+  if (s[0] != '|') return 0;
+  s +=1;
+  p->this->kind = KIND_TABLE;
+
+
+  //p->cursor =   p->this->end = cr_nextLine(p, s);
+  Node *row;
+  Node *tail = NULL;
+
+  while (1) {
+
+    row = pool_new(p);
+    row->kind = KIND_TABLE_ROW;
+
+    if (tail)   tail = tail->next = row;
+    else p->this->children = tail = row;
+
+    row->start = s;
+    p->cursor = s =   row->end = p->this->end = cr_nextLine(p, s);
+
+    if (row->end[-1] == '\n') row->end -= 1;
+    while(row->end[-1] == ' ' ) row->end -= 1;
+    if (row->end[-1] == '|') row->end -= 1;
+
+    if (!*s) break;
+
+    // blanks *not* normalized
+    s = cr_skipBlanks(p, p->cursor);
+    if (s[0] != '|') break;
+    s++;
+
+  }
+  return 1;
+
+};
+//}}}
+static int isParagraph(Parser *p){//{{{
+
+  char *s = p->cursor;
+  p->this->start = s;
+
+  s = cr_nextLine(p, s);
+  p->cursor = p->this->end = s;
+  p->this->kind = KIND_PARAGRAPH;
+  return 1;
+
+}
+//}}}
+
+static void cr_parse(Parser *p, char* z){//{{{
+
+  p->previous = pool_new(p);
+  p->previous->kind = KIND_PARA_BREAK;
+
+  p->this = pool_new(p);
+  p->this->kind = KIND_PARA_BREAK;
+
+  p->inLink = 0;
+  p->inTable = 0;
+
+  p->cursor = z;
+  p->list = NULL;
+  p->istack = NULL;
+
+  while (p->cursor[0]) {
+
+    while (1){
+
+      // must be first
+      if (isNoWikiBlock(p)) break;
+      if (isParaBreak(p))   break;
+
+      // order not important
+      if (isMacro(p)) break;
+      if (isHeading(p)) break;
+      if (isHorizontalRule(p)) break;
+      if (isListItem(p)) break;
+      if (isTable(p)) break;
+
+      // here for efficiency?
+      if (isEndWikiMarker(p)) break;
+
+      // must be last
+      if (isParagraph(p)); break;
+
+      // doh!
+      assert(0);
+    }
+
+    int kind = p->this->kind;
+    int prev = p->previous->kind;
+
+    if (kind & KIND_END_WIKI_MARKER)  return;
+
+    if (kind == KIND_PARAGRAPH && prev & KIND_LIST_OR_PARAGRAPH) {
+        p->previous->end = p->this->end;
+        p->this = pool_new(p);
+        continue;
+    }
+
+    if ( !(kind & KIND_LIST && prev & KIND_LIST) )
+      cr_render(p, p->previous);
+
+    p->previous = p->this;
+    p->this = pool_new(p);
+
+  }
+}
+//}}}
+
+//}}}
+
+//{{{ MACROS
+LOCAL void do_macro_wiki_contents(Parser *p, Node *n){//{{{
+
+  Stmt q;
+
+  blob_append(p->iblob, "<ul>", 4);
+
+  db_prepare(&q,
+    "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname GLOB 'wiki-*'"
+    " ORDER BY lower(tagname)"
+  );
+  while( db_step(&q)==SQLITE_ROW ){
+    const char *zName = db_column_text(&q, 0);
+    blob_appendf(p->iblob, "<li><a href=\"%s/wiki?name=%T\">%h</a></li>", g.zBaseURL, zName, zName);
+  }
+  db_finalize(&q);
+  blob_append(p->iblob, "</ul>", 5);
+}//}}}
+
+
+static int cr_match(char *z1, char *z2, int *len){
+  *len = strlen(z2);
+  return !memcmp(z1, z2 ,*len);
+}
+
+int cr_has_macro(char *z, int *tokenType){
+
+  int len;
+
+  if (cr_match(z, "wiki-contents>>", &len)) {
+    *tokenType = MACRO_WIKI_CONTENTS;
+    return len;
+  }
+
+  tokenType = MACRO_NONE;
+  return 0;
+
+}
+//}}}
+
+char *wiki_render_creole(Renderer *r, char *z){
+
+  Parser parser;
+  Parser *p = &parser;
+
+  p->r = r;
+  p->iblob = r->pOut;
+
+  p->nFree = 0;
+  p->pool = NULL;
+
+  cr_parse(p, z);
+
+  cr_render(p, p->previous);
+
+  pool_free(p);
+
+  return p->cursor;
+
+}
+

Index: src/main.mk
==================================================================
--- src/main.mk
+++ src/main.mk
@@ -27,10 +27,11 @@
   $(SRCDIR)/clone.c \
   $(SRCDIR)/comformat.c \
   $(SRCDIR)/configure.c \
   $(SRCDIR)/construct.c \
   $(SRCDIR)/content.c \
+  $(SRCDIR)/creoleparser.c \
   $(SRCDIR)/db.c \
   $(SRCDIR)/delta.c \
   $(SRCDIR)/deltacmd.c \
   $(SRCDIR)/descendants.c \
   $(SRCDIR)/diff.c \
@@ -100,10 +101,11 @@
   clone_.c \
   comformat_.c \
   configure_.c \
   construct_.c \
   content_.c \
+  creoleparser_.c \
   db_.c \
   delta_.c \
   deltacmd_.c \
   descendants_.c \
   diff_.c \
@@ -173,10 +175,11 @@
  $(OBJDIR)/clone.o \
  $(OBJDIR)/comformat.o \
  $(OBJDIR)/configure.o \
  $(OBJDIR)/construct.o \
  $(OBJDIR)/content.o \
+ $(OBJDIR)/creoleparser.o \
  $(OBJDIR)/db.o \
  $(OBJDIR)/delta.o \
  $(OBJDIR)/deltacmd.o \
  $(OBJDIR)/descendants.o \
  $(OBJDIR)/diff.o \
@@ -273,16 +276,16 @@
 	# noop
 
 clean:	
 	rm -f $(OBJDIR)/*.o *_.c $(APPNAME) VERSION.h
 	rm -f translate makeheaders mkindex page_index.h headers
-	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
+	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h creoleparser.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h graph.h http.h http_socket.h http_ssl.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
 
 page_index.h: $(TRANS_SRC) mkindex
 	./mkindex $(TRANS_SRC) >$@
 headers:	page_index.h makeheaders VERSION.h
-	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
+	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h creoleparser_.c:creoleparser.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h graph_.c:graph.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
 	touch headers
 headers: Makefile
 Makefile:
 add_.c:	$(SRCDIR)/add.c translate
 	./translate $(SRCDIR)/add.c >add_.c
@@ -394,10 +397,17 @@
 
 $(OBJDIR)/content.o:	content_.c content.h  $(SRCDIR)/config.h
 	$(XTCC) -o $(OBJDIR)/content.o -c content_.c
 
 content.h:	headers
+creoleparser_.c:	$(SRCDIR)/creoleparser.c translate
+	./translate $(SRCDIR)/creoleparser.c >creoleparser_.c
+
+$(OBJDIR)/creoleparser.o:	creoleparser_.c creoleparser.h  $(SRCDIR)/config.h
+	$(XTCC) -o $(OBJDIR)/creoleparser.o -c creoleparser_.c
+
+creoleparser.h:	headers
 db_.c:	$(SRCDIR)/db.c translate
 	./translate $(SRCDIR)/db.c >db_.c
 
 $(OBJDIR)/db.o:	db_.c db.h  $(SRCDIR)/config.h
 	$(XTCC) -o $(OBJDIR)/db.o -c db_.c

Index: src/makemake.tcl
==================================================================
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -21,10 +21,11 @@
   clone
   comformat
   configure
   construct
   content
+  creoleparser
   db
   delta
   deltacmd
   descendants
   diff

Index: src/style.c
==================================================================
--- src/style.c
+++ src/style.c
@@ -383,10 +383,64 @@
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
 @ }
+@
+@ /* For marking important UI elements which shouldn't be
+@    lightly dismissed. I mainly use it to mark "not yet
+@    implemented" parts of a page. Whether or not to have
+@    a 'border' attribute set is arguable. */
+@ .achtung {
+@   color: #ff0000;
+@   background: #ffff00;
+@   border: 1px solid #ff0000;
+@ }
+@
+@ div.miniform {
+@     font-size: smaller;
+@     margin: 8px;
+@ } 
+@ /* additions to support creole parser */
+@
+@ .creoletable {
+@   border: 1px solid #666666;
+@   border-spacing: 0;
+@   margin: 1.5em 2em 1.8em 2em;
+@ }
+@ .creoletable * tr th {
+@   font-size: 100%;
+@   padding: .5em .7em .5em .7em;
+@   border-left: 1px solid #666666;
+@   background-color: #558195;
+@   vertical-align: bottom;
+@   color: white;
+@   empty-cells: show;
+@ }
+@ .creoletable * tr td {
+@   padding: .4em .7em .45em .7em;
+@   border-left: 1px solid #D9D9D9;
+@   border-top: 1px solid #D9D9D9;
+@   vertical-align: center;
+@   empty-cells: show;
+@ }
+@ .creole-block-nowiki {
+@   background: oldlace;
+@   margin: 2em;
+@   overflow: auto;
+@ }
+@ .creole-inline-nowiki {
+@   background: oldlace;
+@ }
+@ .creole-noimage {
+@   color:green;
+@   border:1px solid green;
+@ }
+@ .creole-nomacro {
+@   color:red;
+@   border:1px solid red;
+@ } 
 ;
 
 /*
 ** WEBPAGE: style.css
 */

Index: src/wiki.c
==================================================================
--- src/wiki.c
+++ src/wiki.c
@@ -379,19 +379,20 @@
   const char *zRemark;
   char *zId;
 
   zDate = db_text(0, "SELECT datetime('now')");
   zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
+  blob_append(p, "\n<<fossil>>\n", -1);
   blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", 
     zId, zDate, g.zLogin);
   free(zDate);
   zUser = PD("u",g.zLogin);
   if( zUser[0] && strcmp(zUser,g.zLogin) ){
     blob_appendf(p, " (claiming to be %h)", zUser);
   }
   zRemark = PD("r","");
-  blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
+  blob_appendf(p, " added:</i><br />\n%s\n<<fossil>>\n</div id=\"%s\">", zRemark, zId);
 }
 
 /*
 ** WEBPAGE: wikiappend
 ** URL: /wikiappend?name=PAGENAME

Index: src/wikiformat.c
==================================================================
--- src/wikiformat.c
+++ src/wikiformat.c
@@ -335,10 +335,13 @@
 #define TOKEN_NUM_LI        7    /*  "  #  " */
 #define TOKEN_ENUM          8    /*  "  \(?\d+[.)]?  " */
 #define TOKEN_INDENT        9    /*  "   " */
 #define TOKEN_RAW           10   /* Output exactly (used when wiki-use-html==1) */
 #define TOKEN_TEXT          11   /* None of the above */
+/* macro tokens - hackyyyy */
+#define TOKEN_FOSSIL        12   /* fossil macro */
+#define TOKEN_CREOLE        13   /* creole macro */
 
 /*
 ** State flags
 */
 #define AT_NEWLINE          0x001  /* At start of a line */
@@ -350,10 +353,11 @@
 #define WIKI_USE_HTML       0x040  /* wiki-use-html option = on */
 
 /*
 ** Current state of the rendering engine
 */
+#if INTERFACE
 typedef struct Renderer Renderer;
 struct Renderer {
   Blob *pOut;                 /* Output appended to this blob */
   int state;                  /* Flag that govern rendering */
   int wikiList;               /* Current wiki list type */
@@ -368,11 +372,11 @@
     short iCode;                 /* Markup code */
     short allowWiki;             /* ALLOW_WIKI if wiki allowed before tag */
     const char *zId;             /* ID attribute or NULL */
   } *aStack;
 };
-
+#endif
 
 /*
 ** z points to a "<" character.  Check to see if this is the start of
 ** a valid markup.  If it is, return the total number of characters in
 ** the markup including the initial "<" and the terminating ">".  If
@@ -556,11 +560,23 @@
 ** z points to the start of a token.  Return the number of
 ** characters in that token.  Write the token type into *pTokenType.
 */
 static int nextWikiToken(const char *z, Renderer *p, int *pTokenType){
   int n;
-  if( z[0]=='<' ){
+  if( !p->inVerbatim && z[0] == '<' && z[1] == '<' ) {
+    if (!memcmp(z, "<<fossil>>", 10)) {
+      *pTokenType = TOKEN_FOSSIL;
+      return 10;
+    }
+#ifdef HAVE_CREOLE_MACRO
+    if (!memcmp(z, "<<creole>>", 10)) {
+      *pTokenType = TOKEN_CREOLE; 
+      return 10;
+    }
+#endif
+  }
+  else if( z[0]=='<' ){
     n = markupLength(z);
     if( n>0 ){
       *pTokenType = TOKEN_MARKUP;
       return n;
     }else{
@@ -1067,10 +1083,32 @@
     if( wikiUseHtml ){
       n = nextRawToken(z, p, &tokenType);
     }else{
       n = nextWikiToken(z, p, &tokenType);
     }
+
+    /*
+    ** Additions to support creole switch
+    */
+    if(tokenType == TOKEN_FOSSIL) {
+      z+=10;
+      continue;
+    }
+#ifdef HAVE_CREOLE_MACRO
+    else if(tokenType == TOKEN_CREOLE) {
+      z = wiki_render_creole(p, z+n);
+    }
+#endif
+
+    /*
+    if (!p->inVerbatim && z[0]=='<' && z[1] == '<') {
+      z = wiki_render_macro(p, z, &tokenType);
+      if (tokenType) continue;
+    }
+    */
+    /* end additions */
+
     p->state &= ~(AT_NEWLINE|AT_PARAGRAPH);
     switch( tokenType ){
       case TOKEN_PARAGRAPH: {
         if( inlineOnly ){
           /* blob_append(p->pOut, " &para; ", -1); */
@@ -1435,5 +1473,45 @@
   if( z[i]!='<' ) return 0;
   blob_init(pTitle, &z[iStart], i-iStart);
   blob_init(pTail, &z[i+8], -1);
   return 1;
 }
+
+/*
+** Additions to support macro extensions
+**
+** This only allows block level macros, not inline macros
+**
+** All macros must recognize '<<fossil>>' in the input
+** stream and return control to fossil.
+*/
+
+char *wiki_render_macro(Renderer *p, char *z, int *tokenType){
+  if (!memcmp(z, "<<fossil>>", 10)){
+    *tokenType = TOKEN_MARKUP; /* Close enough for "in-progress".
+                                  Should have it's own token type. */
+    return z + 10;
+  }
+
+#ifdef HAVE_CREOLE_MACRO
+  if (!memcmp(z, "<<creole>>", 10)) {
+    *tokenType = TOKEN_MARKUP; 
+    return wiki_render_creole(p, z+10);
+  }
+#endif
+
+  *tokenType = 0;
+  return z;
+}
+
+int wf_linkLength(const char *z){
+  return linkLength(z);
+}
+void wf_openHyperlink(
+  Renderer *p,            /* Rendering context */
+  const char *zTarget,    /* Hyperlink traget; text within [...] */
+  char *zClose,           /* Write hyperlink closing text here */
+  int nClose              /* Bytes available in zClose[] */
+){
+  return openHyperlink(p, zTarget, zClose, nClose);
+}
+/* end additions */