commit 0b3456cca6a00c8c465913de0c0f6916278ac36f Author: David Baer Date: Wed Aug 5 22:22:49 2015 -0400 Initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b00763b --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +Makefile +Makefile.bak +Makefile.in +aclocal.m4 +autom4te.cache/ +config.h +config.h.in +config.h.in~ +config.log +config.status +configure +depcomp +install-sh +missing +src/.deps/ +src/Makefile +src/Makefile.in +src/sermon +src/sermon_lexer.c +src/sermon_lexer.o +src/sermon_parser.c +src/sermon_parser.h +src/sermon_parser.o +src/sermon_util.o +stamp-h1 +ylwrap diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..38bdf12 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,2 @@ +SUBDIRS = src +dist_doc_DATA = README diff --git a/README b/README new file mode 100644 index 0000000..9d2bcfd --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +sermon 0.9 +========== + +Sermon markup converter diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..6ccdff9 --- /dev/null +++ b/configure.ac @@ -0,0 +1,11 @@ +AC_INIT([sermon], [0.9], [david.a.baer@gmail.com]) +AM_INIT_AUTOMAKE([-Wall -Werror foreign]) +AC_PROG_CC +AC_PROG_LEX +AC_PROG_YACC +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([ + Makefile + src/Makefile +]) +AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..dd13940 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,5 @@ +bin_PROGRAMS = sermon +BUILT_SOURCES = sermon_lexer.c sermon_parser.c sermon_parser.h +AM_YFLAGS = -d --location +sermon_SOURCES = sermon_lexer.l sermon_parser.y sermon_util.c +LIBS = $(LEXLIB) diff --git a/src/queue.h b/src/queue.h new file mode 100644 index 0000000..fa75c3d --- /dev/null +++ b/src/queue.h @@ -0,0 +1,99 @@ +/*************************************************************************** + * queue.h - macros for defining a double-ended queue + * + * Copyright (C) 2015 by David A. Baer, All Rights Reserved + * + * Description: + * + * These macros are designed to make it easier to process input whose + * length is unknown before it terminates (e.g. a series of numbers). + * The APPEND_QUEUE macro adds a new element to the list. The + * QUEUE_LENGTH macro provides the current length of the queue. The + * QUEUE_TO_ARRAY macro can convert the queue into a more compact + * array. + * + * Usage: + * + * DEFINE_QUEUE(MyType, QueueOfMyType); + * NEW_QUEUE(QueueOfMyType, myq); + * APPEND_QUEUE(QueueOfMyType, myq, VALUE1); + * APPEND_QUEUE(QueueOfMyType, myq, VALUE2); + * ... + * FOREACH_QUEUE(QueueOfMyType, myq, iterator) { + * (some action) + * } + * FOREACH_QUEUE_END <--- NOTE: THIS IS MANDATORY + * len = QUEUE_LENGTH(myq); + * QUEUE_TO_ARRAY(QueueOfMyType, myq, MyType, targetptr); + * + * + */ +#ifndef _QUEUE_H +#define _QUEUE_H +#include + +#define DEFINE_QUEUE(T, N) \ +struct _##N##Node { \ + T data; \ + struct _##N##Node* next; \ +};\ +typedef struct { \ + int length; \ + struct _##N##Node *head, *tail; \ +} N + +#define NEW_QUEUE(T, N) \ + T N = (T) { .length = 0, .head = NULL, .tail = NULL } + +#define APPEND_QUEUE(T, Q, E) { \ + struct _##T##Node* n = (struct _##T##Node*)malloc(sizeof(struct _##T##Node)); \ + if (!n) { perror ("Could not allocate space for new queue element."); exit(1); } \ + n->data = E; \ + n->next = NULL; \ + if ((Q).head == NULL) (Q).head = n; \ + if ((Q).tail != NULL) (Q).tail->next = n; \ + (Q).tail = n; \ + (Q).length++; \ +} + +#define QUEUE_LENGTH(Q) Q.length + +#define FOREACH_QUEUE(T, Q, N) { \ + struct _##T##Node* N = NULL; \ + for (N = (Q).head; N != NULL; N = (N)->next) + +#define FOREACH_QUEUE_END } + +#define DESTROY_QUEUE(T, Q) { \ + struct _##T##Node* ptr = (Q).head; \ + while (ptr != NULL) { \ + struct _##T##Node* temp = ptr->next; \ + free(ptr); \ + ptr = temp; \ + } \ + (Q).head = (Q).tail = NULL; \ + (Q).length = 0; \ +} + +#define DESTROY_QUEUE_FUNC(T, Q, F) { \ + struct _##T##Node* ptr = (Q).head; \ + while (ptr != NULL) { \ + struct _##T##Node* temp = ptr->next; \ + F(ptr->data); \ + free(ptr); \ + ptr = temp; \ + } \ + (Q).head = (Q).tail = NULL; \ + (Q).length = 0; \ +} + +#define QUEUE_TO_ARRAY(QT, Q, T, DST) { \ + int i = 0; \ + DST = (T*)calloc(Q.length,sizeof(T)); \ + if (!DST) { perror("Could not allocate space for array."); exit(1); } \ + FOREACH_QUEUE(QT, Q, ptr) \ + DST[i++] = ptr->data; \ + FOREACH_QUEUE_END; \ +} + +#endif /* !def _QUEUE_H */ diff --git a/src/sermon.h b/src/sermon.h new file mode 100644 index 0000000..4487011 --- /dev/null +++ b/src/sermon.h @@ -0,0 +1,41 @@ +#ifndef _SERMON_H +#define _SERMON_H + +typedef struct { + char* headerType; + char* headerValue; +} SermonHeader; + +typedef enum { + PARA_DEFAULT, + PARA_BLOCKQUOTE +} SermonParagraphType; + +typedef struct { + SermonParagraphType paraType; + char* paraText; +} SermonParagraph; + +typedef struct { + char* refId; + char* refText; +} SermonReference; + +typedef struct { + /* header fields */ + char* sermonTitle; + char* sermonAuthor; + char* sermonDate; + char* sermonOccasion; + char* sermonText; + + int numParagraphs; + SermonParagraph* sermonParagraphs; + int numReferences; + SermonReference* sermonReferences; +} Sermon; + +void InitSermon(Sermon* srm); +void FreeSermon(Sermon* srm); + +#endif /* !def _SERMON_H */ diff --git a/src/sermon_lexer.l b/src/sermon_lexer.l new file mode 100644 index 0000000..899ca6d --- /dev/null +++ b/src/sermon_lexer.l @@ -0,0 +1,35 @@ +%{ +#include +#include "sermon_parser.h" + +#ifdef LEXDEBUG +#define LEXPRINT(x...) fprintf(stderr, x); +#else +#define LEXPRINT(x...) +#endif +%} +%s HEADER HEADERVAL REFERENCEINTRO REFERENCENAME REFERENCE BLOCK +WHITESPACE [ \t] +ID [A-Za-z_][A-Za-z_0-9]* +%% +^[[]\*[^*]*\*\] { /* comment */ LEXPRINT("Comment: %s\n", yytext); } +^[[] { BEGIN(HEADER); yylloc.first_column = yylloc.last_column = 1; return '['; } +\] { BEGIN(INITIAL); yylloc.first_column = ++yylloc.last_column; return ']'; } +
[^:]* { yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) - 1; yylval.sval = strdup(yytext); return HEADERTYPE; } +
:{WHITESPACE}* { BEGIN(HEADERVAL); yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) - 1; return ':'; } +[^\]]* { yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) - 1; yylval.sval = strdup(yytext); return HEADERVALUE; } +^\{ { BEGIN(REFERENCEINTRO); yylloc.first_column = yylloc.last_column = 1; return '{'; } +ref { yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + 2; return KW_REF; } +: { BEGIN(REFERENCENAME); yylloc.first_column = ++yylloc.last_column; return ':'; } +{ID} { yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) + 1; yylval.sval = strdup(yytext); return ID; } +: { BEGIN(REFERENCE); yylloc.first_column = ++yylloc.last_column; return ':'; } +[^}]* { yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) + 1; yylval.sval = strdup(yytext); return REFTEXT; } +[}] { BEGIN(INITIAL); yylloc.first_column = ++yylloc.last_column; return '}'; } +^[^[{>\n].* { yylloc.first_column = ++yylloc.last_column; yylval.sval = strdup(yytext); LEXPRINT("LINE: %s\n", yytext); return LINE; } +^>{WHITESPACE}*\n { yylloc.first_column = yylloc.last_column = 0; yylloc.first_line++; yylloc.last_line++; return '\n'; } +^>{WHITESPACE}* { BEGIN(BLOCK); yylloc.first_column = yylloc.last_column = 1; return '>'; } +..* { BEGIN(INITIAL); yylloc.first_column = yylloc.last_column + 1; yylloc.last_column = yylloc.first_column + strlen(yytext) - 1; yylval.sval = strdup(yytext); return LINE; } +\n { yylloc.first_column = yylloc.last_column = 0; yylloc.first_line++; yylloc.last_line++; return '\n'; } +<> { return 0; } +%% + diff --git a/src/sermon_parser.y b/src/sermon_parser.y new file mode 100644 index 0000000..325143f --- /dev/null +++ b/src/sermon_parser.y @@ -0,0 +1,163 @@ +%{ +#include +#include +#include +#include "queue.h" +#include "sermon.h" + +#ifdef PARSE_DEBUG +#define PARSEPRINT(x...) fprintf(stderr, x); +#else +#define PARSEPRINT(x...) +#endif + +int yylex(); +extern char* yytext; +extern FILE* yyin; + +DEFINE_QUEUE(char *, LineQueue); +DEFINE_QUEUE(SermonParagraph, ParagraphQueue); +DEFINE_QUEUE(SermonReference, ReferenceQueue); + +NEW_QUEUE(LineQueue, lineQ); +NEW_QUEUE(ParagraphQueue, paragraphQ); +NEW_QUEUE(ReferenceQueue, referenceQ); + +char* lineQueueToString(LineQueue* lq) { + int paraLength = 0, idx = 0; + char *dest = NULL; + FOREACH_QUEUE(LineQueue, *lq, ptr) + paraLength += strlen(ptr->data) + 1; + FOREACH_QUEUE_END + paraLength--; + dest = (char*)malloc(paraLength + 1); + memset(dest, 0, paraLength + 1); + FOREACH_QUEUE(LineQueue, *lq, ptr) { + strncat(dest + idx, ptr->data, paraLength - idx); + idx += strlen(ptr->data); + if (ptr->next != NULL) dest[idx++] = ' '; + free(ptr->data); + } + FOREACH_QUEUE_END + dest[idx] = '\0'; + return dest; +} + +void yyerror(Sermon*, const char*); + +%} +%union { + char* sval; + char cval; +} + +%token HEADERTYPE +%token HEADERVALUE +%token ID +%token REFTEXT +%token LINE +%token KW_REF +%parse-param {Sermon* sermon} +%initial-action { +}; +%% +sermon: + headerlist sermontext references { + sermon->numParagraphs = QUEUE_LENGTH(paragraphQ); + sermon->numReferences = QUEUE_LENGTH(referenceQ); + if (sermon->numParagraphs) QUEUE_TO_ARRAY(ParagraphQueue, paragraphQ, SermonParagraph, sermon->sermonParagraphs); + if (sermon->numReferences) QUEUE_TO_ARRAY(ReferenceQueue, referenceQ, SermonReference, sermon->sermonReferences); + DESTROY_QUEUE(ParagraphQueue, paragraphQ); + DESTROY_QUEUE(ReferenceQueue, referenceQ); + } + ; +break: + break '\n' + | /* empty */ + ; +headerlist: + headerlist header break + | /* empty */ + ; +header: + '[' HEADERTYPE ':' HEADERVALUE ']' { + PARSEPRINT("Parsed header %s\n", $2); + if (strcmp($2, "title") == 0) { sermon->sermonTitle = $4; } + else if (strcmp($2, "author") == 0) { sermon->sermonAuthor = $4; } + else if (strcmp($2, "text") == 0) { sermon->sermonText = $4; } + else if (strcmp($2, "occasion") == 0) { sermon->sermonOccasion = $4; } + else if (strcmp($2, "date") == 0) { sermon->sermonDate = $4; } + else { free($4); } + free($2); + } + ; +sermontext: + sermontext block break + | /* empty */ + ; +block: + para { + SermonParagraph p = { + .paraType = PARA_DEFAULT, + .paraText = lineQueueToString(&lineQ) + }; + APPEND_QUEUE(ParagraphQueue, paragraphQ, p); + PARSEPRINT("Parsed paragraph:\n%s\n\n", p.paraText); + } + | blockquote { + SermonParagraph p = { + .paraType = PARA_BLOCKQUOTE, + .paraText = lineQueueToString(&lineQ) + }; + APPEND_QUEUE(ParagraphQueue, paragraphQ, p); + PARSEPRINT("Parsed paragraph:\n%s\n\n", p.paraText); + } + ; +para: + para LINE '\n' { APPEND_QUEUE(LineQueue, lineQ, $2); } + | LINE '\n' { DESTROY_QUEUE(LineQueue, lineQ); APPEND_QUEUE(LineQueue, lineQ, $1); } + ; +blockquote: + blockquote '>' LINE '\n' { APPEND_QUEUE(LineQueue, lineQ, $3); } + | '>' LINE '\n' { DESTROY_QUEUE(LineQueue, lineQ); APPEND_QUEUE(LineQueue, lineQ, $2); } + ; +references: + references reference break + | /* empty */ + ; +reference: + '{' KW_REF ':' ID ':' REFTEXT '}' { SermonReference r = { .refId = $4, .refText = $6 }; APPEND_QUEUE(ReferenceQueue, referenceQ, r); } + ; +%% + +int main(int argc, char* argv[]) { + Sermon sermon; + int i = 0, block = 0, normal = 0; + InitSermon(&sermon); + yyin = fopen(argv[1], "rt"); + yyparse(&sermon); + printf("Parsed sermon.\n"); + printf("TITLE=%s\n", sermon.sermonTitle ? sermon.sermonTitle : "none"); + printf("AUTHOR=%s\n", sermon.sermonAuthor ? sermon.sermonAuthor : "none"); + printf("DATE=%s\n", sermon.sermonDate ? sermon.sermonDate : "none"); + printf("OCCASION=%s\n", sermon.sermonOccasion ? sermon.sermonOccasion : "none"); + printf("TEXT=%s\n", sermon.sermonText ? sermon.sermonText : "none"); + printf("\nThere are %d paragraphs", sermon.numParagraphs); + for (i = 0; i < sermon.numParagraphs; i++) { + if (sermon.sermonParagraphs[i].paraType == PARA_DEFAULT) normal++; + else if (sermon.sermonParagraphs[i].paraType == PARA_BLOCKQUOTE) block++; + } + printf(" (%d regular, %d blockquote)\n", normal, block); + printf("\nThere are %d references.\n", sermon.numReferences); + for (i = 0; i < sermon.numReferences; i++) { + printf(" - %s: %s\n", sermon.sermonReferences[i].refId, sermon.sermonReferences[i].refText); + } + printf("\n"); + FreeSermon(&sermon); + fclose(yyin); +} + +void yyerror(Sermon* s, const char* msg) { + fprintf(stderr, "Parse error (%d:%d): %s - \"%s\"\n", yylloc.first_line, yylloc.first_column, msg, yytext); + exit(1); +} diff --git a/src/sermon_util.c b/src/sermon_util.c new file mode 100644 index 0000000..e1ddb2e --- /dev/null +++ b/src/sermon_util.c @@ -0,0 +1,31 @@ +#include +#include +#include "sermon.h" + +void InitSermon(Sermon* srm) { + memset(srm, 0, sizeof(Sermon)); +} + +void FreeSermon(Sermon* srm) { + int i = 0; + if (srm->sermonTitle) free(srm->sermonTitle); + if (srm->sermonAuthor) free(srm->sermonAuthor); + if (srm->sermonDate) free(srm->sermonDate); + if (srm->sermonOccasion) free(srm->sermonOccasion); + if (srm->sermonText) free(srm->sermonText); + + if (srm->numParagraphs) { + for (i = 0; i < srm->numParagraphs; i++) { + free(srm->sermonParagraphs[i].paraText); + } + free(srm->sermonParagraphs); + } + if (srm->numReferences) { + for (i = 0; i < srm->numReferences; i++) { + free(srm->sermonReferences[i].refId); + free(srm->sermonReferences[i].refText); + } + free(srm->sermonReferences); + } +} +