Inhaltsverzeichnis
dokgen : ein Dokumentationsgenerator für C++
Stimmen Quelltext und Kommentar nicht überein,
sind vermutlich beide falsch.— Sprichwort
Version: 2011-04-29. Projektstatus
Überblick
Quelltext liegt gewöhnlich als "plain-text" vor. Dokumentationen sollen dagegen "pretty printed" sein. Die Druckaufbereitung kann aufwendig sein, andere Dateiformate erfordern und führt zu gedruckten Dokumenten, die nicht unbedingt den aktuellen Stand wiedergeben. Liegen Quelltext und Dokumentation getrennt vor, werden sie sich später oder (eher) früher auseinander entwickeln. Jedes Stück Wissen sollte nur an einer Stelle gespeichert sein. Ideal wäre es, die Dokumentation parallel zur Quelltext-Entwicklung zu schreiben. Und weil Umschalten nervt, sollten beide dicht nebeneinander in einer Datei stehen. Ändert sich etwas, werden Quelle und Dokumentation zusammen überarbeitet.
Die Idee ist nicht neu. Donald E. Knuth's
Literate Programming-System WEB
leistet so etwas für TeX.
Das XML-Format DocBook
entwickelt sich, wenn auch langsam, zu einem Standard
für elektronische Dokumentation und Buchdruck.
Java-Programmierer dokumentieren ihre Quellen
mit dem zum System gehörenden Standardwerkzeug javadoc
.
Für C++ gibt es Doxygen
und mittlerweile nicht mehr aktive Projekte wie ccdoc und DOC++.
Die verschiedenen Systeme verfolgen dabei aber unterschiedliche Ziele.
Literate Programming übersetzt und dokumentiert top-down entworfene Programme,
die TeX-Syntax ist nicht ohne weiteres geläufig.
DocBook kann als Ausgangspunkt zur Erzeugung unterschiedlicher Dateien dienen.
Wie bei WEB geht aber der direkte Bezug zum Quellcode verloren.
Doxygen dokumentiert Entwickler-Schnittstellen
von Klassen und Methoden eines Systems wahlweise in LaTeX und HTML,
eignet sich aber weniger zum Entwickeln einer Abhandlung.
Alle Systeme erfordern Zusatzwerkzeuge
(Pascal WEB oder CWEB, XSL-Transformationen für DocBook etc.)
oder sind mit solchen einfacher nutzbar
(spezielle Editoren, "Quellcode"-Vervollständigung).
Was mir fehlt(e), ist ein System,
mit dem Programmquellen für die Lehre mit Erläuterungen versehen,
getested und schnell als Webseiten aufbereitet werden können.
Es geht um ein System mit einfacher Syntax,
einfacher Handhabung und dem Prinzip "one source".
Die Kombination von C++-Kommentar und
Wiki-artiger Syntax erscheint mir als gangbarer Mittelweg.
dokgen
ist nicht der Stein der Weisen und nicht die Lösung aller Probleme.
Es kollidiert mit der Syntax von Doxygen (3 Schrägstriche).
Vor allem aber ist Dokumentation nicht kostenlos zu haben.
Auch die von dokgen
darzustellende Information
muss schon in der Ausgangsdatei enthalten sein.
Dokumentations- und Quelltextabschnitte mehrerer zusammengehöriger Quelltexte
bilden gemeinsam eine einzige zu pflegende Datei eines Moduls oder Programms:
Mit dokgen
werden aktuelle Dokumente aus den Programmquellen erzeugt.
Daneben können andere Programme den Quelltext für den Übersetzungsprozess aufbereiten.
Mit dem Werkzeug make
lassen sich solche Vorgänge auch automatisieren.
Deutsche Umlaute ÄÖÜäöüß werden mit dem Zeichensatz ISO 8859-1 (ISO Latin-1) übersetzt.
Der Aufruf des Konsolen-Programms erfordert die Umlenkung von Ein- und Ausgabe.
Die Anwendung des Programms auf sein eigenes Quelldokument
dokgen < dokgen.txt > dokgen.htm
erzeugte die aktuell betrachtete Datei.
Dokumentation und Quelltext aus einer Quelle
Die Erläuterungen sind im Quelltext integriert. Dokumentationszeilen beginnen mit genau drei Schrägstrichen am linken Rand:
/// @title dokgen : ein Dokumentationsgenerator /// @author René Richter /// @keywords Dokumentation, automatisiert /// > Stimmen Quelltext und Kommentar nicht überein, /// > sind vermutlich beide falsch. /// >--- Sprichwort /// Version: 2002-11-25. @ref Projektstatus /// Kurz-Dokumentation: @link dokgen0.htm /// === Überblick /// Liegen Quelltext und Dokumentation getrennt vor, /// werden sie sich später oder (eher) früher auseinander entwickeln.
(Um dies zu zeigen, wurde der Anfang dieses Dokumentes um ein Zeichen eingerückt.) Quelltext sind dagegen "normale" Zeilen:
// bei Übersetzung aus fortlaufendem Quelltext (ohne extract-Prozess) #define ALL_IN_ONE
Kommentare :
am Zeilenanfang von Quelltextabschnitten
und
~
//: dateiname // Dateianfang // Dateiinhalt //~ // Dateiende
sind vorgesehen, um mit
extract < dokgen.txt
die Quelltexte einzelner Module aus der Originaldatei herausziehen zu können.
Dann ist das Makro ALL_IN_ONE
unbekannt
und die Header müssen eingebunden werden.
Die Programme extract
und dokstrip
(siehe Anhang)
sollen später das System ergänzen.
Syntax
Dokumentkopf
Jedes Dokument kann mit den Angaben
@title | Überschrift |
@author | Autor (nur am Dokumentanfang) |
@keywords | Suchbegriffe (nur am Dokumentanfang) |
beginnen. Die Einträge gehen bis zum Zeilenende. Mehrzeilige Einträge sind in einen geschweiften Klammerblock einzuschließen. Alle Angaben sind optional. Sobald alle Einträge festgelegt sind, eine Leerzeile folgt oder normaler Text folgt, ist der Dokumentkopf abgeschlossen.
Überschriften
Lange Texte erfordern eine Gliederung. Es erfolgt keine automatische Nummerierung.
=== | 1 Kapitel |
==== | 1.1 Unterkapitel |
===== | 1.1.1 Abschnitt |
====== | 1.1.1.1 Unterabschnitt |
/// === 1 Kapitel /// ==== 1.1 Unterkapitel /// ===== 1.1.1 Abschnitt /// ====== 1.1.1.1 Unterabschnitt
Textelemente
Der Dokumentationstext ist strukturierbar mit
- Listen
- ohne Nummern,
- mit Nummern,
- von Begriffsdefinitionen, z.B.
- Begriff : erläuternder Text
- Tabellen,
- eingerücktem Text und
- Zitaten.
Textelemente beginnen mit Marken in der fünften Spalte:
| | Tabelle. |
: | Eingerückter Text. Jeder Doppelpunkt ist eine Ebene. |
> | Zitat. Die Herkunft wird durch >— markiert. |
* | Liste. |
# | Nummerierte Liste. |
; | Definitionsliste. |
Listen beliebigen Typs sind schachtelbar:
/// * Listen /// *# ohne Nummern, /// *# mit Nummern, /// *# von Begriffsdefinitionen, z.B. /// *#; Begriff : erläuternder Text
Mehrzeilige Listeneinträge sind in einen Klammerblock einzuschließen.
Tabellenzellen beginnen immer mit einem senkrechten Strich. Optional kann dem Strich ein Ausrichtungspfeil folgen.
|< | linksbündig |
|^ | zentriert |
|> | rechtsbündig |
ohne Pfeil | Standardausrichtung |
/// |< linksbündig /// |^ zentriert /// |> rechtsbündig /// | Standardausrichtung
Eingerückter Text eignet sich besonders für Algorithmenbeschreibungen (Pseudocode):
für alle Dokumentzeilenverarbeiten
/// : für alle Dokumentzeilen /// :: verarbeiten
Zitate beginnen mit einem Zeichen >
(wie in Mailsystemen).
Die Herkunft kann nach >—
angegeben werden:
/// > Suddenly, documentation isn't so bad. /// >--- Andrew Hunt, David Thomas: The Pragmatic Programmer
Suddenly, documentation isn't so bad.— Andrew Hunt, David Thomas: The Pragmatic Programmer
Marken im Fließtext
Im Dokumentabschnitten können Marken eingefügt werden.
@code | Schlüsselwort im Text |
@image | Bild |
@label | Verweisziel innerhalb des Dokuments |
@link | Verweis außerhalb des Dokuments (Hypertextreferenz) |
@ref | Verweis innerhalb des Dokuments |
In @code{…}
-Abschnitten dürfen
keine geschweiften Klammern vorkommen.
Es kann ein beliebiges anderes Zeichen
als Begrenzer eingesetzt werden,
das nicht innerhalb des Blocks vorkommt:
@code|{…}|
erzeugt {…}
.
Bild- und Verweismarken folgt eine Bezeichnung ohne Leerzeichen. Optional kann ein geschweifter Klammerblock mit einem Text folgen (Bildunterschrift oder Beschreibung des Verweiszieles).
/// Siehe @ref Anhang {Anhang und Projektstatus}.
Siehe Anhang und Projektstatus.
Implementierung des dokgen-Systems
Das Programm dokgen
besteht aus mehreren Klassen:
- ParseError zur Beschreibung von Fehlern bei der Verarbeitung,
- Lexer zur lexikalischen Analyse,
- Parser führt die syntaktische Analyse durch und schickt die Inhalte zum Ausgabeziel.
- Target ist ein einfaches Ausgabeziel zur Demonstration und zum Debuggen.
- HTMLTarget generiert ein HTML-Dokument.
- DokuWikiTarget erzeugt einen DokuWiki-Quelltext.
Weitere Ziele (Ausgabeformate) können aus der Target-Klasse abgeleitet werden. Dazu muss das Hauptprogramm angepasst werden.
Fehlerzustand
//: parseerr.hpp : dokgen Fehlermeldungen - R.Richter 2002-11-18 ///////////////////////////////////////////////////////////////////// #ifndef PARSEERR_HPP #define PARSEERR_HPP #include <string> #include <sstream> class ParseError { public: ParseError(std::string why, int row, int column, std::string line) { std::ostringstream out; out << "Fehler in Zeile " << row << ": " << why << "\n" << line << "\n" << std::string(column, ' ') << "^"; msg_ = out.str(); } char const * what() const { return msg_.c_str(); } private: std::string msg_; }; #endif // PARSEERR_HPP //~
Ein einfaches Testprogramm zeigt z.B., dass beim ß ein Fehler gefunden wurde:
// #define PARSEERRTEST #ifdef PARSEERRTEST //: errtst.cpp : dokgen Fehlermeldungen - R.Richter 2002-11-18 ///////////////////////////////////////////////////////////////////// #include <iostream> #ifndef ALL_IN_ONE #include "parseerr.hpp" #endif // ALL_IN_ONE int main() { ParseError err("Was weiß ich", 1, 7, "Was weiß ich"); std::cout << err.what() << '\n'; return 0; } //~ #endif // PARSEERRTEST
Lexikalische Analyse
Der Eingabestrom wird in kleine Einheiten (Symbole, engl. token) zerlegt, die vom Parser interpetiert werden. Die Zeilen sollten weniger als 300 Zeichen enthalten.
//: lexer.hpp : dokgen lexikalische Analyse - R.Richter 2002-11-18 ///////////////////////////////////////////////////////////////////// #ifndef LEXER_HPP #define LEXER_HPP #include <iostream> #include <string> #ifndef ALL_IN_ONE #include "parseerr.hpp" #endif // ALL_IN_ONE class Lexer { public: Lexer(std::istream& input); char const* next_token(); // try to read token and return it as ... char const* token() const { return token_; } enum Tokentype { END, NEWLINE, CHAR, TAG }; enum State { EOT, DOC, CODE }; Tokentype token_type() const; State state() const; ParseError error(std::string why) const; private: void next_line(); enum { MAXLENGTH = 300 }; char line_ [MAXLENGTH+1]; char token_[MAXLENGTH+1]; int row_, column_; std::istream& input_; }; // ---[ implementation details ]------------------------------------- inline Lexer::Lexer(std::istream& input) : input_(input) , row_(0) { token_[0] = '\n'; token_[1] = '\0'; next_token(); } inline ParseError Lexer::error(std::string why) const { return ParseError(why, row_, column_, line_); } inline void Lexer::next_line() { line_[0] = '\0'; input_.getline(line_, MAXLENGTH); ++row_; column_ = 0; } #endif // LEXER_HPP //~
Die eingelesenen Zeilen werden in Quelltext und Dokumentation unterteilt. Codezeilen werden als Ganzes übernommen. Dokumentationstext wird weiter untergliedert bis zu Einzelzeichen und Textmarken (Tags).
//: lexer.cpp : dokgen lexikalische Analyse - R.Richter 2002-11-18 ///////////////////////////////////////////////////////////////////// #include <cctype> #include <cstring> #ifndef ALL_IN_ONE #include "lexer.hpp" #endif // ALL_IN_ONE // enum Tokentype { END, NEWLINE, CHAR, TAG }; Lexer::Tokentype Lexer::token_type() const { if (EOT == state()) return END; if (CODE == state()) return CHAR; // in DOC line ... if (0 == std::strcmp(token(), "\n\n")) return NEWLINE; if ('@' == token()[0] && std::isalpha(token()[1])) return TAG; /* else */ return CHAR; } // enum State { EOT, DOC, CODE }; Lexer::State Lexer::state() const { // depends on line and input stream if (!input_) return EOT; if (0 == std::strncmp(line_, "///", 3) && '/' != line_[3]) return DOC; return CODE; } char const* Lexer::next_token() { if ('\n' == token_[0]) // wenn vorher Zeilenumbruch, { next_line(); // dann neue Zeile holen if (DOC == state()) // in Dok-Zeile { column_ = (' ' != line_[3])? 3:4; // "/// " überlesen } } token_[0] = line_[column_++]; // 1 Zeichen holen, int n = 1; if ('\0' == token_[0]) // bei Zeilenende { token_[0] = '\n'; // '\n' in token kopieren if (DOC == state() && '\n' == input_.peek()) { token_[n++] = input_.get(); // evtl. Leerzeile als Absatz } } else if (CODE == state()) { while ('\0' != line_[column_]) // ganze Codezeile auf einmal { token_[n++] = line_[column_++]; } } else if (DOC == state()) { if('@' == token_[0]) // Tag { while (std::isalpha(line_[column_])) // Tagname { token_[n++] = line_[column_++]; } } } token_[n] = '\0'; // Zeichenkette abschließen return token(); } //~
Das Testprogramm zeigt, welche Token erkannt werden:
// #define LEXERTEST #ifdef LEXERTEST //: lexertst.cpp : Dokgen Testprogramm Lexer - R.Richter 2002-11-19 ///////////////////////////////////////////////////////////////////// #include <iostream> #include <string> #include <sstream> #ifndef ALL_IN_ONE #include "lexer.hpp" #endif // ALL_IN_ONE int main() { std::string teststring = "/// @title Test: ÄÖÜäöüß @\n" "\n" "/// === Das Testdokument\n" "/// dient als Testbeispiel\n" "/// * Zwei Möglichkeiten:\n" "/// *# Es klappt sofort.\n" "/// *# Es geht schief.\n" "/// > Es geht immer schief.\n" "/// >--- Murphy\n" "/// :: Probiere nochmal\n" "/// : solange es schiefgeht\n" "/// Quelltextzeilen beginnen nicht mit @code{///}\n" "/// und werden in @code{Maschinenschrift} gesetzt:\n" "// C++ Kommentare @title\n" "////////////////////////\n" "int main() { return 0; }\n" "/* endet mit star slash */"; std::istringstream input( teststring ); Lexer lex(input); std::cout << "state \ttype \tchar \ttoken\n"; while (Lexer::END != lex.token_type()) { std::cout << lex.state() << '\t' << lex.token_type() << '\t'; unsigned char first = *lex.token(); if (std::isprint(first) && !std::isspace(first)) { std::cout << first; } else { std::cout << "ASC " << int(first); } std::cout << '\t' << lex.token() << '\n'; if ('\n' == lex.token()[0]) { // ist nun wirklich kein Fehler ... nur Debugging std::cout << lex.error("Zeile verarbeitet:").what() << '\n'; } lex.next_token(); } return 0; } //~ #endif // LEXERTEST
Einfache Ausgabe
Dokumente in einen Ausgabestrom zu schicken, ist Aufgabe dieser Klasse. Für die Elemente eines Dokumentes werden Ausgabemethoden festgelegt. Die Klasse dient als Ausgangspunkt für bestimmte Ausgabeziele, z.B. HTML-Ausgabe.
//: target.hpp : dokgen Basis Ausgabeziel - R.Richter 2002-11-20 ///////////////////////////////////////////////////////////////////// #ifndef TARGET_HPP #define TARGET_HPP #include <iostream> #include <string> class Target // mit Debug-Ausgaben: { public: Target(std::ostream& output) : output_(output) { } ~Target() { output_.flush(); } virtual void document_start() { output_ << "DOCUMENT Start\n"; } virtual void make_title(std::string author, std::string title, std::string keywords) { output_ << "AUTHOR: " << author << '\n' << "TITLE : " << title << '\n' << "KEYWORDS: " << keywords << '\n'; } virtual void translate(std::string text) { output_ << text; } virtual void paragraph_start() { output_ << "\nPARAGRAPH\n"; } virtual void paragraph_end() { output_ << "\nPARAGRAPH END\n"; } virtual void next_line() { output_ << "\n"; } virtual void label(std::string text) { output_ << "LABEL:(" << text << ")"; } virtual void link(std::string url, std::string caption) { output_ << "LINK:(" << url << " | " << caption << ")"; } virtual void image(std::string name, std::string caption) { output_ << "IMAGE:(" << name << " | " << caption << ")"; } virtual void ref(std::string label, std::string caption) { output_ << "REF:(" << label << " | " << caption << ")"; } virtual void section_start(std::string title) { output_ << "SECTION:(" << title << ")"; } virtual void subsection_start(std::string title) { output_ << "SUBSECTION:(" << title << ")"; } virtual void subsubsection_start(std::string title) { output_ << "SUBSUBSECTION:(" << title << ")\n"; } virtual void subsubsubsection_start(std::string title) { output_ << "SUBSUBSUBSECTION:(" << title << ")\n"; } virtual void section_end(std::string title) { output_ << "SECTION_END:(" << title << ")\n"; } virtual void subsection_end(std::string title) { output_ << "SUBSECTION_END:(" << title << ")\n"; } virtual void subsubsection_end(std::string title) { output_ << "SUBSUBSECTION_END:(" << title << ")\n"; } virtual void subsubsubsection_end(std::string title) { output_ << "SUBSUBSUBSECTION_END:(" << title << ")\n"; } virtual void inlinecode(char begin, std::string text, char end) { output_ << "CODE: " << begin << text << end; } virtual void code_start() { output_ << "CODE Start \n"; } virtual void code(std::string text) { output_ << text; } virtual void code_end() { output_ << "CODE End \n"; } virtual void table_start() { output_ << "TABLE Start\n"; } virtual void table_cell_start(char alignment=' ') { output_ << "TABLE Cell Start"<< alignment<<"\n"; } virtual void table_cell_end() { output_ << "TABLE Cell End\n"; } virtual void table_row_start() { output_ << "TABLE Row Start\n"; } virtual void table_row_end() { output_ << "TABLE Row End\n"; } virtual void table(std::string text) { output_ << text; } virtual void table_end() { output_ << "TABLE End\n"; } virtual void list_start(char type) { switch (type) { case '*': break; case '#': output_ << "NUM"; break; case ';': output_ << "DEFINITION"; break; default : break; } output_ << "LIST Start\n"; } virtual void list_item_start(char type, std::string text) { switch (type) { case '*': // FALLTHROUGH case '#': output_ << "LIST Item Start\n"; break; case ';': output_ << "DEFINITIONLIST Item " << text << " Start\n"; break; default : break; } } virtual void list(std::string text) { output_ << text; } virtual void list_item_end(char type) { switch (type) { case '*': // FALLTHROUGH case '#': case ';': output_ << "LIST Item End\n"; break; default : break; } } virtual void list_end(char type) { switch (type) { case '*': break; case '#': output_ << "NUM"; break; case ';': output_ << "DEFINITION"; break; default : break; } output_ << "LIST End\n"; } virtual void quote_start() { output_ << "QUOTE Start\n"; } virtual void quote(std::string text) { output_ << text; } virtual void quote_from_start() { output_ << "QUOTE from Start\n"; } virtual void quote_from_end() { output_ << "QUOTE from Start\n"; } virtual void quote_end() { output_ << "\nQUOTE End\n"; } virtual void indentation_start() { output_ << "\nIndented text\n"; } virtual void indent(int depth = 1, bool firstline = false) { output_ << std::string(2*depth,' '); } virtual void indentation_end() { output_ << "\nIndented text end\n"; } virtual void document_end() { output_ << "DOCUMENT End\n"; } protected: std::ostream& output_; }; #endif // TARGET_HPP //~
Syntax-Analyse
Dies ist der zentrale Teil eines Übersetzers. Das Durchmustern (engl. parse) der Eingabe setzt die lexikalische Analyse voraus.
//: parser.hpp : dokgen syntaktische Analyse - R.Richter 2002-11-20 ///////////////////////////////////////////////////////////////////// #ifndef PARSER_HPP #define PARSER_HPP #include <string> #ifndef ALL_IN_ONE #include "lexer.hpp" #include "target.hpp" #endif // ALL_IN_ONE
Die vom Lexer erkannten Symbole der Sprache müssen einer formalen Grammatik (Syntax) genügen:
/* // --- syntactic elements document ::= dochead { textelement } { section } END dochead ::= { docheadtag { space } docheadline } [ newparagraph | not_DOC ] docheadtag ::= "@author" | "@title" | "@keywords" docheadline ::= '{' { docheadline } '}' { space } [ newline ] | space | docchar } [ newline ] section ::= sectionhead { textelement } { section } sectionhead ::= sectionlevel { space } docheadline [ newline ] sectionlevel ::= "===" | "====" | "=====" | "======" textelement ::= sourcecode | table | list | quote | indentedtext | paragraph sourcecode ::= { anychar | '\n' } not_CODE paragraph ::= { docline } [ newline ] docline ::= { texttoken } [ newline ] texttoken ::= space | docchar | taggedelement taggedelement ::= codesnippet | image | link | reference | label codesnippet ::= "@code" { space } codelimiter { docchar } codelimiter codelimiter ::= '{' | '}' | docchar // as pair // same char before and after image ::= "@image" { space } URL caption link ::= "@link" { space } URL caption reference ::= "@ref" { space } URL caption label ::= "@label" { space } URL URL ::= { nonspace } caption ::= { space } [ '{' docchar '}' ] table ::= { tablerow } tablerow ::= { '|' [ cellalignment ] { texttoken } } newline cellalignment ::= '<' | '^' | '>' list ::= { { prevlistleveltag } listtype listitem } prevlistleveltag ::= '*' | '#' | ';' // all but last tag listtype ::= '*' | '#' | ';' defineditem definededitem ::= { docchar | space } ':' listitem ::= docline | '{' listelement '}' { space } newline listelement ::= sourcecode | listline | table | list | quote | indentedtext listline ::= { listtoken } [ newline ] listtoken ::= space | docchar_but_not_closing_brace | taggedelement quote ::= { '>' docline } [ ">---" quotedfrom ] quotedfrom ::= docline indentedtext ::= { ':' { ':' } docline } // --- lexical elements space ::= '\t' | ' ' in DOC state newline ::= '\n' in DOC state newparagraph ::= "\n\n" in DOC state docchar ::= anychar and state == DOC code ::= lines of any_char in CODE state, separated by '\n' */ class Parser { public: Parser(Lexer& lexer, Target& target) : lexer_(lexer), target_(target) { } void document(); private: void dochead(); std::string docheadline(std::string& text); int section(int level = 0); int sectionlevel(); void textelement(); void paragraph(); void sourcecode(); void docline(); void taggedelement(); void image(); void reference(); void link(); void label(); void codesnippet(); std::string URL(std::string& text); std::string caption(std::string& text); void table(); void list(); void adapt_list_level(char old[], const char actual[]); void listline(); void quote(); void indentedtext(); void spaces(); void brace(char paren); // expects parenthesis std::string collect(std::string& text, char end); std::string nonspace(std::string& text); void debug(std::string msg); Lexer& lexer_; Target& target_; }; #endif // PARSER_HPP //~
Die Methoden der Klasse entsprechen den syntaktischen Elementen des Eingabe-Dokuments.
Das Durchmustern wird mit der syntaktischen Wurzel document()
angestoßen.
//: parser.cpp : dokgen syntaktische Analyse - R.Richter 2002-11-20 ///////////////////////////////////////////////////////////////////// #include <cctype> #include <cstring> #ifndef ALL_IN_ONE #include "parser.hpp" #endif // ALL_IN_ONE void Parser::debug(std::string msg) { std::cerr << msg << '\n' << "state \ttype \tchar \ttoken" << '\n' << lexer_.state() << '\t' << lexer_.token_type() << '\t'; unsigned char first = *lexer_.token(); if (std::isprint(first) && !std::isspace(first)) { std::cerr << first; } else { std::cerr << "ASC " << int(first); } std::cerr << '\t' << lexer_.token() << '\n'; if ('\n' == lexer_.token()[0]) { // ist nun wirklich kein Fehler ... nur Debugging std::cerr << lexer_.error("Zeile verarbeitet:").what() << '\n'; } }
Ein Dokument beginnt mit einem Dokumentenkopf. Nach beliebig vielen Textelementen wird das Dokument durch Abschnittsüberschriften gegliedert.
void Parser::document() { // document ::= dochead { textelement }{ section } END target_.document_start(); dochead(); // before EOT or '=' in DOC state ... while (Lexer::END != lexer_.token_type() && !(Lexer::DOC == lexer_.state() && '=' == *lexer_.token() ) ) { textelement(); } // now reached EOT or '=' in DOC state if (Lexer::DOC == lexer_.state() && '=' == *lexer_.token() ) { int level = 0; while (Lexer::DOC == lexer_.state()) { level = section(level); } } target_.document_end(); }
Der Dokumentkopf wird erstellt, wenn alle Kopfangaben festgelegt sind bzw. sobald Inhalte erkannt werden, die nicht zum Dokumentkopf gehören (Quelltext, normale Erläuterungen).
void Parser::dochead() { // dochead ::= { docheadtag { space } docheadline } [ newparagraph | not_DOC ] // docheadtag ::= "@author" // | "@title" // | "@keywords" // dochead ends when all docheadtags are defined, // or doctext element // or when in state CODE or EOT std::string author, keywords, title; // solange in state DOC und Autor, Titel oder Schlüssel unbekannt while ( Lexer::DOC == lexer_.state() && ("" == author || "" == title || "" == keywords) ) { if (Lexer::TAG == lexer_.token_type()) { // falls authortag, Autor einlesen // falls titletag, Titel einlesen (Titellänge nur eine Zeile?) std::string *text = 0; if (std::string(lexer_.token()) == "@author" ) text = &author; if (std::string(lexer_.token()) == "@keywords") text = &keywords; if (std::string(lexer_.token()) == "@title" ) text = &title; if (text) // Tag erkannt { lexer_.next_token(); spaces(); docheadline(*text); } else break; // andere Tags im Kopf nicht erlaubt, anfangen } else break; // falls irgendwas anderes, anfangen } target_.make_title(author, title, keywords); if (Lexer::NEWLINE == lexer_.token_type()) { lexer_.next_token(); } } std::string Parser::docheadline(std::string& text) { // docheadline ::= '{' { docheadline } '}' { space } [ newline ] // | { space | docchar } [ newline ] char begin = *lexer_.token(); char end = '\n'; if ('{' == begin) { brace(begin); spaces(); end = '}'; } while (end != *lexer_.token()) { if (Lexer::DOC != lexer_.state()) { throw lexer_.error("char must be in DOC line"); } text += lexer_.token(); lexer_.next_token(); } brace(end); if ('}' == end) { spaces(); if (Lexer::NEWLINE != lexer_.token_type() && '\n' == *lexer_.token()) { target_.translate("\n"); lexer_.next_token(); } } return text; }
Abschnittsüberschriften beginnen mit mehreren Gleichheitszeichen. Abschnitte können verschachtelt werden.
int Parser::section(int level) { // section ::= sectionhead { textelement } { section } // sectionhead ::= sectionlevel { space } docheadline [ newline ] // sectionhead if (level == 0) // otherwise already read { level = sectionlevel(); } spaces(); std::string text; docheadline(text); switch (level) { case 3: target_.section_start(text); break; case 4: target_.subsection_start(text); break; case 5: target_.subsubsection_start(text); break; case 6: target_.subsubsubsection_start(text); break; default: lexer_.error("invalid section level"); break; } if (Lexer::NEWLINE == lexer_.token_type()) { lexer_.next_token(); } // textelements while (Lexer::END != lexer_.token_type() && !(Lexer::DOC == lexer_.state() && '=' == *lexer_.token() ) ) { textelement(); } // subsections int next_level = 0; if (Lexer::DOC == lexer_.state() && '=' == *lexer_.token() ) { next_level = sectionlevel(); while (next_level > level) { next_level = section(next_level); } } // section end switch (level) { case 3: target_.section_end(text); break; case 4: target_.subsection_end(text); break; case 5: target_.subsubsection_end(text); break; case 6: target_.subsubsubsection_end(text); break; default: lexer_.error("invalid section level"); // should not happen break; } return next_level; }
Die Überschriftenart wird durch die Anzahl der Gleichheitszeichen festgelegt.
int Parser::sectionlevel() { // sectionlevel ::= "===" | "====" | "=====" | "======" int level = 0; while ('=' == *lexer_.token()) { ++level; lexer_.next_token(); } return level; }
Fließtext besteht aus Absätzen, Tabellen, Listen, Zitaten, eingerücktem Text oder Quelltextzeilen.
void Parser::textelement() { // textelement ::= sourcecode // | table | list // | quote | indentedtext // | paragraph if (Lexer::CODE == lexer_.state()) { sourcecode(); } else { switch (*lexer_.token()) { case '|': table(); break; case '*': case '#': case ';': list(); break; case '>': quote(); break; case ':': indentedtext(); break; default : paragraph(); break; } } }
In jedem Abschnitt ist am Zeilenanfang zu entscheiden, welche Art von Textelement als nächstes zu verarbeiten ist:
void Parser::paragraph() { //paragraph ::= { docline } [ newline ] target_.paragraph_start(); while (Lexer::DOC == lexer_.state() && !strchr("|*#;>:=", *lexer_.token()) && Lexer::NEWLINE != lexer_.token_type() ) { docline(); } if (Lexer::NEWLINE == lexer_.token_type()) { lexer_.next_token(); } target_.paragraph_end(); }
Quelltextabschnitte werden zeilenweise weitergegeben:
void Parser::sourcecode() { // sourcecode ::= { anychar | '\n' } not_CODE target_.code_start(); while (Lexer::CODE == lexer_.state()) { target_.code(lexer_.token()); lexer_.next_token(); } target_.code_end(); }
"Normale" Zeilen bestehen aus Zeichen oder Textmarken
void Parser::docline() { // docline ::= { texttoken } [ newline ] // texttoken ::= space | docchar | taggedelement while (Lexer::DOC == lexer_.state() && '\n' != *lexer_.token()) { if ('@' == *lexer_.token()) { taggedelement(); } else { if ('\\' == *lexer_.token()) // linebreak { lexer_.next_token(); if ('\n' == *lexer_.token()) { target_.next_line(); } else { target_.translate("\\"); } } else { target_.translate(lexer_.token()); lexer_.next_token(); } } } if (Lexer::NEWLINE != lexer_.token_type()) { target_.translate("\n"); std::cerr << lexer_.token() << "\n"; lexer_.next_token(); } }
Textmarken enthalten Quelltextschnipsel, Bilder oder Verweise:
void Parser::taggedelement() { // taggedelement ::= codesnippet | image | link | reference | label if ( std::string(lexer_.token()) == "@code" ) codesnippet(); else if (std::string(lexer_.token()) == "@image") image(); else if (std::string(lexer_.token()) == "@ref" ) reference(); else if (std::string(lexer_.token()) == "@link" ) link(); else if (std::string(lexer_.token()) == "@label") label(); else { target_.translate(lexer_.token()); // normal char lexer_.next_token(); } }
Die Marke @code
kann statt geschweifter Klammern (Standard)
auch ein beliebiges anderes Zeichen als Begrenzer akzeptieren.
void Parser::codesnippet() { // codesnippet ::= "@code" { space } codelimiter { docchar } codelimiter // codelimiter ::= '{' | '}' | docchar // as pair // same char before and after lexer_.next_token(); spaces(); char begin = '{', end = '}'; if ('{' != *lexer_.token()) { begin = end = *lexer_.token(); } lexer_.next_token(); std::string text; target_.inlinecode(begin, collect(text, end), end); }
Bilder und Verweise referenzieren einen Namen. Nachfolgend kann in geschweiften Klammern ein beschreibender Text eingegeben werden.
void Parser::image() { // image ::= "@image" { space } URL caption lexer_.next_token(); spaces(); std::string name, text; URL(name); caption(text); target_.image(name, text); } void Parser::reference() { // reference ::= "@ref" { space } URL caption lexer_.next_token(); spaces(); std::string name, text; URL(name); caption(text); target_.ref(name, text); } void Parser::link() { // link ::= "@link" { space } URL caption lexer_.next_token(); spaces(); std::string name, text; URL(name); caption(text); target_.link(name, text); } void Parser::label() { // label ::= "@label" { space } URL lexer_.next_token(); spaces(); std::string name; URL(name); target_.label(name); } std::string Parser::URL(std::string& text) { // URL ::= { nonspace } return nonspace(text); } std::string Parser::caption(std::string& text) { // caption ::= { space } [ '{' docchar '}' ] spaces(); if ('{' == *lexer_.token()) { lexer_.next_token(); collect(text, '}'); } return text; }
Innerhalb einer Tabelle bewirken senkrechte Striche den Übergang zur nächsten Zelle, Zeilenvorschübe den Übergang in die nächste Zeile. Mehrspaltige bzw. mehrzeilige Bereiche sind nicht vorgesehen.
void Parser::table() { // table ::= { tablerow } // tablerow ::= { '|' [ cellalignment ] { texttoken } } newline // cellalignment ::= '<' | '^' | '>' target_.table_start(); while ('|' == *lexer_.token()) { target_.table_row_start(); while ('\n' != *lexer_.token()) { lexer_.next_token(); char align = ' '; switch (*lexer_.token()) { case '<': case '>': case '^': align = *lexer_.token(); lexer_.next_token(); break; default: break; } target_.table_cell_start(align); while ('|' != *lexer_.token() && '\n' != *lexer_.token()) { if (Lexer::TAG == lexer_.token_type()) { taggedelement(); } else { target_.translate(lexer_.token()); lexer_.next_token(); } } target_.table_cell_end(); } if (Lexer::NEWLINE != lexer_.token_type()) { lexer_.next_token(); } target_.table_row_end(); } target_.table_end(); }
Listeneinträge beginnen mit Stern, Doppelkreuz oder Semikolon. Mehrzeilige Einträge müssen geklammert werden.
void Parser::list() { // list ::= { { prevlistleveltag } listtype listitem } // prevlistleveltag ::= '*' | '#' | ';' // all but last tag // listtype ::= '*' // | '#' // | ';' defineditem // definededitem ::= { docchar | space } ':' const int MAXDEPTH = 100; char previoustags[1+MAXDEPTH] = ""; char actualtags [1+MAXDEPTH] = ""; while (Lexer::DOC == lexer_.state() && std::strchr("*#;", *lexer_.token())) { // read actual head int level=0; while (std::strchr("*#;", *lexer_.token())) { actualtags[level++] = *lexer_.token(); if (level >= MAXDEPTH) throw lexer_.error("maximum list depth exceeded"); lexer_.next_token(); } actualtags[level] = '\0'; adapt_list_level(previoustags, actualtags); char type = actualtags[level-1]; if (';' == type) // defined item { std::string definedname; collect(definedname, ':'); target_.list_item_start(type, definedname); } else { target_.list_item_start(type, ""); } spaces(); // listitem ::= docline // | '{' listline { listelement } '}' { space } newline // listelement ::= sourcecode | listline // | table | list // | quote | indentedtext if ('{' == *lexer_.token()) // process item { lexer_.next_token(); listline(); while ('}' != *lexer_.token()) { if (Lexer::CODE == lexer_.state()) { sourcecode(); } else { switch (*lexer_.token()) { case '|': table(); break; case '*': case '#': case ';': list(); break; case '>': quote(); break; case ':': indentedtext(); break; default : listline(); break; } } } brace('}'); spaces(); if (level > 1 || Lexer::NEWLINE != lexer_.token_type()) { lexer_.next_token(); // eat newline } } else { docline(); } // 2007-03-16 (-): target_.list_item_end(type); } actualtags[0] = '\0'; adapt_list_level(previoustags, actualtags); // Listen schliessen } void Parser::adapt_list_level(char old[], const char actual[]) { // count matching chars int matching = 0; while (old[matching] && old[matching] == actual[matching]) { ++matching; } int oldlevel = strlen(old); int actuallevel = strlen(actual); // close nonmatching list type levels while (matching < oldlevel) { char oldtype = old[--oldlevel]; target_.list_item_end(oldtype); target_.list_end(oldtype); old[oldlevel] = '\0'; } // close list item of same type on same level if (matching > 0 && matching == actuallevel) { target_.list_item_end(old[matching-1]); } // else ... // open list level(s) if (actuallevel > matching + 1) { // warning: you should open list levels one by one throw lexer_.error("list level should be increased by 1 only"); } while (matching < actuallevel) { target_.list_start(actual[matching]); old[matching] = actual[matching]; old[++matching] = '\0'; } } void Parser::listline() { // listline ::= { listtoken } [ newline ] // listtoken ::= space | docchar_but_not_closing_brace | taggedelement while (Lexer::DOC == lexer_.state() && '}' != *lexer_.token()) { if ('@' == *lexer_.token()) { taggedelement(); } else { if ('\\' == *lexer_.token()) // linebreak { lexer_.next_token(); if( '\n' == *lexer_.token() ) { target_.next_line(); } else { target_.translate("\\"); } } else { target_.translate(lexer_.token()); lexer_.next_token(); } } } if (Lexer::NEWLINE == lexer_.token_type()) { target_.next_line(); lexer_.next_token(); } }
Zitate sind durch >
markiert.
void Parser::quote() { // quote ::= { '>' docline } [ ">---" quotedfrom ] // quotedfrom ::= docline target_.quote_start(); while (Lexer::DOC == lexer_.state() && '>' == *lexer_.token()) { lexer_.next_token(); if ('-' == *lexer_.token()) { while ('-' == *lexer_.token()) { lexer_.next_token(); } target_.quote_from_start(); docline(); target_.quote_from_end(); } else { docline(); } } target_.quote_end(); }
Eine Folge von Doppelpunkten bewirkt Einrückung der Textzeile
void Parser::indentedtext() { // indentedtext ::= { ':' { ':' } docline } target_.indentation_start(); int lineNr = 0; while (Lexer::DOC == lexer_.state() && ':' == *lexer_.token()) { int depth = 0; while (':' == *lexer_.token()) { ++depth; lexer_.next_token(); } target_.indent(depth, 0 == lineNr++); docline(); } target_.indentation_end(); }
An einigen Stellen müssen Leerzeichen in der Dokumentation übergangen werden:
void Parser::spaces() { while (Lexer::EOT != lexer_.state() && std::strchr(" \t", *lexer_.token())) { lexer_.next_token(); // Leerzeichen auffressen } }
Marken enden bzw. beginnen mit einem bestimmten Zeichen. Wenn dieses nicht gefunden wird, ist ein Fehler zu melden.
void Parser::brace(char paren) { if (*lexer_.token() == paren && Lexer::DOC == lexer_.state()) { lexer_.next_token(); } else { std::string errormsg = "X expected"; errormsg[0] = paren; throw lexer_.error(errormsg); } }
Der Text bis zum End-Zeichen wird aufgesammelt und in der Zeichenkette abgelegt. Der Text darf die vorgegebene Länge nicht überschreiten.
std::string Parser::collect(std::string& text, char end) { while (*lexer_.token() != end) { if (lexer_.state() != Lexer::DOC) { throw lexer_.error("char must be in DOC line"); } text += lexer_.token(); lexer_.next_token(); } brace(end); return text; }
Text bis zu einem Leerzeichen wird aufgesammelt:
std::string Parser::nonspace(std::string& text) { while (!isspace(*lexer_.token())) { if (lexer_.state() != Lexer::DOC) { throw lexer_.error("char must be in DOC line"); } text += lexer_.token(); lexer_.next_token(); } return text; } //~
Der Parser arbeitet mit Lexer als Eingabegerät und Target als Ausgabegerät zusammen:
// #define PARSETEST #ifdef PARSETEST //: parsetst.cpp : Testprogramm Lexer - R.Richter 2002-11-20 ////////////////////////////////////////////////////////////////////////// #include <cstdlib> #include <iostream> #include <sstream> #ifndef ALL_IN_ONE #include "parser.hpp" #endif // ALL_IN_ONE int main() { std::string teststring = "/// @title Test: ÄÖÜäöüß @\n" "\n" "/// @image onesource.bmp { blabla }\n" "\n" "/// === Das Testdokument\n" "/// dient als Testbeispiel\n" "/// * Zwei Möglichkeiten:\n" "/// *# Es klappt sofort.\n" "/// *# { Es geht schief.\n" "/// Das ist normal...\n" "/// } \n" "/// > Es geht immer schief.\n" "/// >--- Murphy\n" "/// :: Probiere nochmal\n" "/// : solange es schiefgeht\n" "/// | Tabelle | 3 Spalten | ausgerichtet\n" "/// |< links |^Mitte |> rechts\n" "\n" "/// ==== 1.1 Unterabschnitt\n" "/// ==== 1.2 Unterabschnitt\n" "/// @link dokgen.htm { Dokumentation }\n" "\n" "/// === Abschnitt Quellcode\n" "/// Quelltextzeilen beginnen nicht mit @code{///}\n" "/// und werden in @code{Maschinenschrift} gesetzt:\n" "// C++ Kommentare @title\n" "////////////////////////\n" "int main() { return 0; }\n" "/* endet mit star slash */"; std::istringstream input(teststring); Lexer lex(input); Target target(std::cout); Parser parser(lex, target); try { parser.document(); } catch(ParseError e) { std::cerr << e.what() << '\n'; return EXIT_FAILURE; } return EXIT_SUCCESS; } //~ #endif // PARSETEST
HTML-Ausgabe
Online-Dokumentation im HTML-Format ist eine spezielle, wichtige Form eines Targets. Die Ausformung ist nicht kompliziert, nur mühsam.
//: htmltarget.hpp : dokgen Ausgabeziel HTML - R.Richter 2002-11-20 ///////////////////////////////////////////////////////////////////// #ifndef HTMLTARGET_HPP #define HTMLTARGET_HPP #ifndef ALL_IN_ONE #include "target.hpp" #endif // ALL_IN_ONE class HTMLTarget : public Target { public: HTMLTarget(std::ostream& output) : Target(output) { } void document_start() { output_ << "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" " "\"http://www.w3.org/TR/html4/loose.dtd\">\n" "<html>\n" "<head>\n" " <link rel=stylesheet type=\"text/css\" href=\"docstyle.css\">\n" " <meta http-equiv=\"Content-Type\" content=\"text/html; " "charset=iso-8859-1\">\n" " <meta name=\"GENERATOR\" content=\"dokGen 0.10 - R.Richter\">\n"; } void make_title(std::string author, std::string title, std::string keywords) { if (author != "") { output_ << " <meta name=\"author\" content=\""; translate(author); output_ << "\">\n"; } if (keywords != "") { output_ << " <meta name=\"keywords\" content=\""; translate(keywords); output_ << "\">\n"; } if (title != "") { output_ << "<title>"; translate(title); output_ << "</title>\n"; } output_ << "</head>\n" "<body><a name=\"top\"></a>\n" "\n" "<!-- Document starts here -->\n" "\n"; if (title != "") { output_ << "<h1 class=\"title\">"; translate(title); output_ << "</h1>\n"; } if (author != "") { output_ << "<h2 class=\"author\">"; translate(author); output_ << "</h2>\n"; } if (keywords != "") { output_ << "<p>"; translate("Suchbegriffe: "); translate(keywords); output_ << "</p>\n"; } } void next_line() { output_ << "<br>"; } void label(std::string text) { output_ << "<a name=\"" << text << "\"></a>"; } void link(std::string url, std::string caption) { output_ << "<a href=\"" << url << "\">"; translate(caption != "" ? caption : url); output_ << "</a>"; } void image(std::string name, std::string caption) { output_ << "<img src=\"" << name << "\""; if (caption != "") { output_ << " alt = \"" << caption << "\"><br>"; translate(caption); } else { output_ << " alt = \"Bild " << name << "\">"; } } void ref(std::string label, std::string caption) { output_ << "<a href=\"#" << label << "\">"; translate(caption != "" ? caption : label); output_ << "</a>"; } void section_start(std::string title) { output_ << "\n<h3>"; translate(title); output_ << "</h3>"; } void subsection_start(std::string title) { output_ << "\n<h4>"; translate(title); output_ << "</h4>"; } void subsubsection_start(std::string title) { output_ << "\n<h5>"; translate(title); output_ << "</h5>"; } void subsubsubsection_start(std::string title) { output_ << "\n<h6>"; translate(title); output_ << "</h6>"; } void section_end(std::string title) { // do nothing } void subsection_end(std::string title) { // do nothing } void subsubsection_end(std::string title) { // do nothing } void subsubsubsection_end(std::string title) { // do nothing } void paragraph_start() { output_ << "\n<p>"; } void paragraph_end() { output_ << "\n</p>"; } void inlinecode(char begin, std::string text, char end) { output_ << "<code>"; translate(text); output_ << "</""code>"; } void code_start() { output_ << "<pre>"; } void code(std::string text) { translate(text); } void code_end() { output_ << "</pre>\n"; } void table_start() { output_ << "<table border=\"1\">\n"; } void table_cell_start(char alignment = ' ') { output_ << "\t\t<td"; switch (alignment) { case '<': output_ << " align=\"left\""; break; case '>': output_ << " align=\"right\""; break; case '^': output_ << " align=\"center\""; break; default: break; } output_ << ">"; } void table_cell_end() { output_ << "\t\t</td>\n"; } void table_row_start() { output_ << "\t<tr>\n"; } void table_row_end() { output_ << "\t</tr>\n"; } void table(std::string text) { translate(text); } void table_end() { output_ << "</table>\n"; } void list_start(char type) { switch (type) { case '*': output_ << "\n<ul>";break; case '#': output_ << "\n<ol>"; break; case ';': output_ << "\n<dl>"; break; default: break; } } virtual void list_item_start(char type, std::string text) { switch (type) { case '*': // FALLTHROUGH case '#': output_ << "\n<li>"; break; case ';': output_ << "\n<dt>"; translate(text); output_ << "</dt>\n<dd>"; break; default: break; } } void list(std::string text) { translate(text); } virtual void list_item_end(char type) { switch (type) { case '*': // FALLTHROUGH case '#': output_ << "</li>\n"; break; case ';': output_ << "</dd>\n"; break; default: break; } } void list_end(char type) { switch (type) { case '*': output_ << "</ul>\n";break; case '#': output_ << "</ol>\n"; break; case ';': output_ << "</dl>\n"; break; default: break; } } void quote_start() { output_ << "<blockquote class=\"quotation\">"; } void quote(std::string text) { translate(text); output_ << '\n'; } void quote_from_start() { output_ << "<div class=\"quotedfrom\">"; } void quote_from_end() { output_ << "</div>\n"; } void quote_end() { output_ << "</blockquote>\n"; } void document_end() { output_ << "\n" "<!-- Document ends here -->\n" // "\n" // "<a href=\"#top\">zurück zum Seitenanfang</a>\n" "</body></html>\n"; } virtual void indentation_start() { output_ << "\n<div class=\"pseudocode\">\n"; } virtual void indentation_end() { output_ << "\n</div>\n"; } void indent(int level, bool firstline) { if (!firstline) output_ << "\n<br>"; while (level-->0) { output_ << " "; } } void translate(std::string text) { std::string::iterator iter = text.begin(); while (iter != text.end()) { switch (*iter) { case '<' : output_ << "<"; break; case '>' : output_ << ">"; break; case '&' : output_ << "&"; break; // german Umlauts, ISO-8859-1 (ISO-Latin1) assumed case '\xC4': output_ << "Ä"; break; case '\xE4': output_ << "ä"; break; case '\xD6': output_ << "Ö"; break; case '\xF6': output_ << "ö"; break; case '\xDC': output_ << "Ü"; break; case '\xFC': output_ << "ü"; break; case '\xDF': output_ << "ß"; break; default: output_ << *iter; break; } ++iter; } } }; #endif // HTMLTARGET_HPP //~
Für Testzwecke kann eine Mini-Dokumentation aus einer Zeichenkette erzeugt werden.
// #define HTMLTEST #ifdef HTMLTEST //: htmltst.cpp : dokgen Test HTMLtarget - R.Richter 2002-11-21 ///////////////////////////////////////////////////////////////////// #include <fstream> #include <sstream> // noch unbekannt in Dev C++, g++ #ifndef ALL_IN_ONE #include "parser.hpp" #include "htmltarget.hpp" #endif // ALL_IN_ONE int main() { std::string teststring = "/// @title dokgen : ein Dokumentationsgenerator\n" "/// @author René Richter\n" "/// @keywords Dokumentation, automatisiert\n" "\n" "/// > Willst du dir ein @code{Juwel} schleifen,\\\n" "/// > solltest du wenigstens mit @code{Rohdiamant} beginnen.\n" "/// >--- Steve McConnell\n" "/// System-Dokumentation: @link dokgen.htm {ausführlicher} Version: 2002-12-02.\n" "\n" "/// === Überblick\n" "/// DokGen ist ein Programmierwerkzeug, das es erleichtert,\n" "/// aktuelle Dokumente aus den Programmquellen zu erzeugen.\n" "\n" "/// @image onesrc.gif\n" "\n" "/// Die Erläuterungen sind im Quelltext integriert.\n" "/// Dokumentationszeilen beginnen mit genau drei Schrägstrichen am linken Rand.\n" "/// ==== Dokumentkopf\n" " /// @title dokgen : ein Dokumentationsgenerator\n" " /// @author René Richter\n" " /// @keywords Dokumentation, automatisiert\n" "/// Alle Angaben sind optional.\n" "/// === Überschriften\n" " /// === Abschnitt\n" " /// ==== Unterabschnitt\n" " /// ===== weitere ...\n" " /// ====== letzte (4.) Gliederungsebene\n" "/// ==== Textelemente\n" "/// Der Dokumentationstext ist strukturierbar mit\n" "/// * Listen\n" "/// *# ohne Nummern,\n" "/// *# mit Nummern,\n" "/// *# von Begriffsdefinitionen, z.B.\n" "/// *#; Begriff : erläuternder Text\n" "/// * Tabellen,\n" "/// * eingerücktem Text und\n" "/// * Zitaten.\n" "\n" "/// Listen beliebigen Typs sind schachtelbar:\n" " /// * Listen\n" " /// *# ohne Nummern,\n" " /// *# mit Nummern,\n" " /// *# von Begriffsdefinitionen, z.B.\n" " /// *#; Begriff : erläuternder Text\n" "/// Mehrzeilige Listeneinträge sind in einen Klammerblock einzuschließen.\n" "\n" "/// Tabellenzellen beginnen immer mit einem senkrechten Strich.\n" "/// Optional kann dem Strich eine Ausrichtungspfeil folgen.\n" "/// | @code{|<} | linksbündig \n" "/// | @code{|^} | zentriert \n" "/// | @code{|>} | rechtsbündig. \n" "/// | ohne | Standardausrichtung\n" " /// | @code{|<} | linksbündig \n" " /// | @code{|^} | zentriert \n" " /// | @code{|>} | rechtsbündig. \n" " /// | ohne Pfeil| Standardausrichtung\n" "\n" "/// Eingerückter Text eignet sich besonders für Algorithmenbeschreibungen\n" "/// (Pseudocode):\n" "/// : für alle Dokumentzeilen\n" "/// :: verarbeiten\n" " /// : für alle Dokumentzeilen\n" " /// :: verarbeiten\n" "/// Zitate beginnen mit einem Zeichen @code|>| (wie in Mailsystemen).\n" "/// Die Herkunft kann nach @code|>---| angegeben werden:\n" " /// > Suddenly, documentation isn't so bad.\n" " /// >--- Andrew Hunt, David Thomas: The Pragmatic Programmer\n" "/// > Suddenly, documentation isn't so bad.\n" "/// >--- Andrew Hunt, David Thomas: The Pragmatic Programmer\n" "\n" "/// ==== Marken im Fließtext\n" "/// Im Dokumentabschnitten können Marken eingefügt werden.\n" "/// | @code{@code} | Schlüsselwort im Text \n" "/// | @code{@image} | Bild\n" "/// | @code{@label} | Verweisziel innerhalb des Dokuments \n" "/// | @code{@link} | Verweis außerhalb des Dokuments (Hypertextreferenz)\n" "/// | @code{@ref} | Verweis innerhalb des Dokuments\n" "\n" "/// In @code|@code{...}|-Abschnitten dürfen\n" "/// keine geschweiften Klammern vorkommen.\n" "/// Es kann ein beliebiges anderes Zeichen\n" "/// als Begrenzer eingesetzt werden,\n" "/// das nicht innerhalb des Blocks vorkommt:\n" "/// @code+@code|{...}|+ erzeugt @code|{...}|. \n" "/// Bild- und Verweismarken folgt eine Bezeichnung ohne Leerzeichen.\n" "/// Optional kann ein geschweifter Klammerblock mit einem Text folgen\n" "/// (Bildunterschrift oder Beschreibung des Verweiszieles).\n" " /// Ausführliche Dokumentation siehe @link dokgen.htm {Anhang und Projektstatus}.\n" "/// Ausführliche Dokumentation siehe @link dokgen.htm {Anhang und Projektstatus}.\n" ; std::istringstream input(teststring); std::ofstream output("dokgen0.htm"); Lexer lex(input); HTMLTarget target(output); Parser parser(lex, target); try { parser.document(); } catch(ParseError e) { std::cerr << e.what() << '\n'; } return 0; } //~ #endif // HTMLTEST
DokuWiki-Ausgabe
Optional können DokuWiki-Quellen erzeugt werden.
//: dokuwikitarget.hpp : dokgen Basis Ausgabeziel - R.Richter 2011-04-27 //////////////////////////////////////////////////////////////////////// #ifndef DOKUWIKITARGET_HPP #define DOKUWIKITARGET_HPP #ifndef ALL_IN_ONE #include "target.hpp" #endif // ALL_IN_ONE class DokuWikiTarget : public Target { public: DokuWikiTarget(std::ostream& output) : Target(output) , listLevel_(0) { } void document_start() {} void make_title(std::string author, std::string title, std::string keywords) { output_ << "====== " << title << " ======\n" << "Autor: " << author << "\\\\\n" << "Schlagworte: " << keywords << '\n'; } void paragraph_start() {} void paragraph_end() { output_ << "\n\n"; } void next_line() { output_ << "\\\\\n"; } void label(std::string text) {} void link(std::string url, std::string caption) { output_ << "[[" << url; if (caption != "") output_ << "|" << caption; output_ << "]]"; } void image(std::string name, std::string caption) { output_ << "{{" << name; if (caption != "") output_ << "|" << caption; output_ << "}}"; } void ref(std::string label, std::string caption) { output_ << "[[#" << label; if (caption != "") output_ << "|" << caption; output_ << "]]"; } void section_start(std::string title) { output_ << "===== " << title << " =====\n"; } void subsection_start(std::string title) { output_ << "==== " << title << " ====\n"; } void subsubsection_start(std::string title) { output_ << "=== " << title << " ===\n"; } void subsubsubsection_start(std::string title) { output_ << "== " << title << " ==\n"; } void section_end(std::string title) {} void subsection_end(std::string title) {} void subsubsection_end(std::string title) {} void subsubsubsection_end(std::string title) {} void inlinecode(char begin, std::string text, char end) { output_ << "\'\'" << text << "\'\'"; } void code_start() { output_ << "<code cpp>\n"; } void code(std::string text) { output_ << text; } void code_end() { output_ << "</""code>\n"; } // prevent Dokuwiki from interpreting void table_start() {} void table_cell_start(char alignment=' ') { output_ << "|"; switch (alignment) { case '>': output_ << " "; break; case '^': output_ << " "; break; default: break; } } void table_cell_end() {} void table_row_start() {} void table_row_end() { output_ << "|\n"; } void table(std::string text) { output_ << text; } void table_end() {} void list_start(char type) { switch (type) { case '*': // FALLTHROUGH case '#': case ';': ++listLevel_; break; default : break; } } void list_item_start(char type, std::string text) { output_ << std::string(2*listLevel_, ' '); switch (type) { case '*': output_ << "* "; break; case '#': output_ << "- "; break; case ';': output_ << "* **" << text << "** : "; break; default : break; } } void list(std::string text) { output_ << text; } void list_item_end(char type) {} void list_end(char type) { switch (type) { case '*': // FALLTHROUGH case '#': case ';': --listLevel_; break; default : break; } } void quote_start() { output_ << "<file>\n"; } void quote(std::string text) { output_ << text; } void quote_from_start() { output_ << "--- "; } void quote_from_end() { output_ << ""; } void quote_end() { output_ << "</file>\n"; } void indentation_start() {} void indentation_end() {} void indent(int depth = 1, bool firstline = false) { output_ << std::string(depth, '>') << ' '; } void document_end() {} private: int listLevel_; }; #endif // DOKUWIKITARGET_HPP //~
Mehrzeilige Listeneinträge sind im Ausgabetext nachzubearbeiten, da diese von DokuWiki nicht unterstützt werden.
Hauptprogramm
Die Verarbeitung-Pipeline
std::cin » lex » parser » target » std::cout
wird zusammengesetzt und die Verarbeitung angestoßen.
Bei Fehlern wird vom Parser
eine Ausnahme ParseError
geworfen, die abgefangen werden muss, um das Programm ordentlich zu beenden.
#define DOKGEN_MINIMAL #ifdef DOKGEN_MINIMAL //: dokgen.cpp : Dokumentationsgenerator - R.Richter 2002-11-23 ///////////////////////////////////////////////////////////////////// #include <cstdlib> #ifndef ALL_IN_ONE #include "parser.hpp" #include "htmltarget.hpp" #include "dokuwikitarget.hpp" #endif // ALL_IN_ONE int main(int argc, char* argv[]) { Lexer lex(std::cin); HTMLTarget html(std::cout); DokuWikiTarget dokuwiki(std::cout); Target* target = &html; if (argc == 2 && std::string(argv[1]) == "--dokuwiki") target = &dokuwiki; Parser parser(lex, *target); try { parser.document(); } catch(ParseError e) { std::cerr << e.what() << '\n'; return EXIT_FAILURE; } return EXIT_SUCCESS; } //~ #endif // DOKGEN_MINIMAL
Anhang
Zur Vervollständigung des dokgen
-Systems gehören
die Werkzeuge dokstrip und
extract.
Über eine CSS-Datei docstyle.css
lässt sich das Aussehen der entstandenen HTML-Seiten anpassen.
Die Quelltexte der Programme sind als
ZIP-Datei
verfügbar. Nach Umbenennen der Endungen *.txt
in *.cpp
sollten die Quellen z.B. mit make dokgen
übersetzbar sein.
Projektstatus
Status: Experimentell. Erzeugt gültigen HTML 4.01 Transitional Code.
Versionsgeschichte
Datum | Version | Beschreibung |
2011-04-29 | 0.10 | Erst-Veröffentlichung |
2011-04-27 | 0.10 | Target DokuWiki ergänzt |
2011-04-26 | 0.10 | Übersetzt / getestet mit g++ 4.6.0 |
2007-03-18 | 0.09 | Bild onesrc.bmp → onesrc.gif, Versionsgeschichte |
2007-03-17 | 0.09 | W3C HTML 4.01 Trasnsitional Validierung bestanden |
2007-03-17 | 0.09 | <img> standalone Element, Tags in Kleinschreibung |
2007-03-17 | 0.09 | Syntaxänderung: paragraph ist textelement, document, section |
2007-03-16 | 0.09 | Syntaxänderung: verschachtelte Listen in listitem |
2004-09-09 | 0.08 | Neuübersetzung wegen Systemupdate (msvcrt.dll) |
2002-12-09 | 0.07 | setup.bat im Hauptverzeichnis |
2002-12-09 | 0.06 | dokgen-system und Testumgebung, make-Workflow |
2002-12-08 | 0.05 | Dokumentation Werkzeuge docstrip etc. umgestellt |
2002-12-03 | 0.04 | Syntaxänderung: Tabellen und Listen im Wiki-Stil |
2002-11-26 | 0.03 | mit HTML-Dokumentation |
2002-11-25 | 0.02 | Dokumentation ergänzt |
2002-11-25 | 0.01 | Projektstart, erste lauffähige Version |
Fehler und Mängel
- Option
--dokuwiki
: Mehrzeilige Listeneinträge sind für DokuWiki manuell nachzubearbeiten