Sfoglia il codice sorgente

moved documentation backend to A2 trunk

git-svn-id: https://svn.inf.ethz.ch/svn/lecturers/a2/trunk@6330 8c9fc860-2736-0410-a75d-ab315db34111
felixf 9 anni fa
parent
commit
23fbcfd7d2

BIN
source/Fox.Tool


+ 1845 - 0
source/FoxDocumentationBackend.Mod

@@ -0,0 +1,1845 @@
+(**
+	The special backend in the Fox compiler suite serves as a documentation generator to produce documents from Oberon source files.
+	#author# Felix Friedrich
+	#purpose# Documentation Generator
+*)
+
+MODULE FoxDocumentationBackend;
+
+(**
+@concept
+= Documentation Backend
+
+The [[FoxDocumentationBackend|documentation backend]] is mainly a tool to merge different informations, namely:
+	# The information from the symbol and module scope structure
+	# The information provided by documenting comments
+
+Documentation comments that immediately precede a symbol or that are directly in the same line as a symbol are associated with this symbol.
+Therefore the documentation contained within the respective comment is on the same level as the description of the symbol itself and will be
+displayed together with the information about the symbol.
+
+The following describes how a documentation is generated from a module:
+	* Association to symbols and preprocessing
+		## A comment that follows a symbol X and stands in the same line as X is associated with this symbol X.
+		## Comments that follow the MODULE (or CELLNET) specification are treated separately to describe the module.
+		## Any other comment that stand in front of some symbol Y is associated with this symbol Y.
+		## Consecutive comments are always merged as character arrays mefore being parsed by the comment parser.
+	* Module documentation
+		## Each comment (or sequence of consecutive comments) is parsed by the comment parser into a separate document
+		## During processing of a module, each symbol is described in a separate symbol section. The module itself is described in the module summary section.
+		## Each comment document is merged with the respective symbol section in the overal module document.
+	* Module documentation merging
+		## When more than one modules are processed into one documentation file, the module documentats are merged into one global document.
+		## The header of the document contains a linked list of all involved modules.
+
+== Formatting elements
+=== Paragraphs and line breaks
+	Text is written in paragraphs. Paragraphs are separated by a blank line. Lines that are not separated by a blank line are considered as contiguous text (such as in LaTex)
+	Explicit line breaks can be inserted using two backslashes:
+
+	{{{ This is a line \\ that is broken }}}
+
+	This is a line \\ that is broken
+=== Text Styles
+	Text can  be written boldface, underlined or  in italics. Left and right delimiters of such emphasized text may not be separated from the text by a whitespace.
+
+	|=Name |= Code Example |= Translation |
+	| Boldface | {{{ *example text*}}} | *Boldface Text* |
+	| Underline | {{{ _example text_}}} | _Underline Text_ |
+	| Italics | {{{ /example text/ }}} | /Italics Text/ |
+
+=== bullets and lists
+	Bulleted or numbered lists start with an asterisk or number sign, respectively. Description start with a text that is embraced by number signs.
+
+{{{	* bullet 1
+	** sub bullet1
+	** sub bullet2
+* bullet 2
+
+# number 1
+	## sub number 1
+	## sub number 2
+# number 2
+
+#description label# description text
+#description label2# description text2 }}}
+
+	Result
+		* bullet 1
+			** sub bullet1
+			** sub bullet2
+		* bullet 2
+
+		# number 1
+			## sub number 1
+			## sub number 2
+		# number 2
+
+		#description label# description text
+		#description label2# description text2
+
+=== Labels and links
+	A label in the text is denoted as follows
+
+	{{{ <<labelName>> }}} <<labelName>>
+
+	A link in the text is denoted as
+
+	{{{ [[labelName]] }}}
+
+	or, if an alternative text should be displayed, it can be written as
+
+	{{{ [[labelName|alternative text] }}}
+
+=== Document Structure
+	Documents consist of sections. A section is started with a sequence of {{{@}}} letter as the first characters on a separate line. Examples:
+
+	{{{ @ title of a section of level 1 }}}
+
+	{{{ @@ title of a section of level 2 }}}
+
+	If the first text element of the title is a label, then this lable is attributed to the section.
+
+	{{{ @ <<label>> title }}}
+
+	This can be written shorter: If there is no whitespace between the leading section letter and a string, the string is also counted as the label of the section:
+
+	{{{ @label title }}}
+
+	For structuring a section, headings are provided. A heading is started with an equal sign as the first character in a separate line. Examples
+
+{{{	= title of a paragraph of level 1
+	== title of a paragraph of level 2
+	=label title
+	= <<label>> title }}}
+
+=== Tables
+
+	Tables can be written by writing cells in a separate paragraph. A cell is either a header cell or a data cell. Header cells start with a vertical bar and an equal sign
+	while data cells are embraced by vertical lines only:
+
+	{{{
+	|= header 1 |= header 2 |= header 3
+	| data 1 | data 2 | data 3
+	| data 4 | data 5 | data 6 }}}
+
+	Result:
+
+	|= header 1 |= header 2 |= header 3
+	| data 1 | data 2 | data 3
+	| data 4 | data 5 | data 6 }}}
+
+**)
+
+
+
+IMPORT Basic := FoxBasic, SyntaxTree := FoxSyntaxTree, Global := FoxGlobal, Scanner := FoxScanner, Backend := FoxBackend, Formats := FoxFormats,
+		Options, Streams, Runtime, Strings, SymbolFileFormat := FoxBinarySymbolFile, Diagnostics,
+		DocumentationTree := FoxDocumentationTree, DocumentationPrinter := FoxDocumentationPrinter, DocumentationHtml := FoxDocumentationHtml, DocumentationParser := FoxDocumentationParser, DocumentationScanner := FoxDocumentationScanner,
+		D := Debugging, Files;
+
+CONST
+	Section=0;
+	Item=1;
+	Enum=2;
+	MaxLevels=3;
+
+	VisibleConstant*=3; (** visible constant comment **)
+
+	(** second visible constant *)
+	SecondVisibleConstant*=4;
+	Third*=4;
+
+	DefaultTemplateFile="oc/DocuTemplate.txt";
+	
+	KeywordElementType = ElementType.Bold;
+	QualifiedIdentifierElementType = ElementType.Bold;
+
+TYPE
+
+	ElementType=DocumentationTree.ElementType;
+	ParagraphType=DocumentationTree.ParagraphType;
+
+	State= RECORD
+		document: DocumentationTree.Document;
+		section: DocumentationTree.Section;
+		paragraphs: DocumentationTree.Paragraphs;
+		paragraph: DocumentationTree.Paragraph;
+		text: DocumentationTree.Text;
+		scope: SyntaxTree.Scope;
+	END;
+
+	(** Printer object to write the documentation to a stream. Implemented as visitor on the Syntax Tree.*)
+	Generator*= OBJECT (SyntaxTree.Visitor)
+	VAR
+		w,ws: Streams.StringWriter;
+		case: LONGINT;
+		diagnostics: Diagnostics.Diagnostics;
+
+		(* transient state variables *)
+		level: ARRAY MaxLevels OF LONGINT;
+
+		current: State;
+		document: DocumentationTree.Document;
+		parameterDocument: DocumentationTree.Document;
+		short: BOOLEAN;
+		backend: DocumentationBackend;
+
+		PROCEDURE &Init*(diagnostics: Diagnostics.Diagnostics);
+		VAR i: LONGINT;
+		BEGIN
+			NEW(w,1024); NEW(ws, 64); case := Scanner.Uppercase;
+			FOR i := 0 TO MaxLevels-1 DO level[i] := 0 END;
+			SELF.diagnostics := diagnostics;
+			current.document := NIL; current.section := NIL; current.paragraphs := NIL; current.text := NIL;
+			document := NIL;
+		END Init;
+
+		(* helper procedures *)
+
+		PROCEDURE Keyword(CONST a: ARRAY OF CHAR);
+		VAR
+			str: ARRAY 64 OF CHAR;
+		BEGIN
+			IF case= Scanner.Lowercase THEN Small(a,str) ELSE COPY(a,str) END;
+			w.String(str);
+			ToText(w,current.text,KeywordElementType);
+		END Keyword;
+
+		PROCEDURE Identifier*(x: SyntaxTree.Identifier);
+		VAR str: Scanner.IdentifierString;
+		BEGIN
+			Basic.GetString(x,str); w.String(str);ToText(w,current.text,ElementType.Default)
+		END Identifier;
+
+		(** Procedure used to traverse qualified identifiers **)
+		PROCEDURE QualifiedIdentifier*(x: SyntaxTree.QualifiedIdentifier);
+		VAR str: Scanner.IdentifierString;
+		BEGIN
+			IF x.prefix # SyntaxTree.invalidIdentifier THEN Basic.GetString(x.prefix,str); w.String(str); w.String("."); END;
+			Basic.GetString(x.suffix,str); w.String(str); ToText(w,current.text,QualifiedIdentifierElementType);
+		END QualifiedIdentifier;
+
+		(* types *)
+
+		PROCEDURE Type*(x: SyntaxTree.Type);
+		BEGIN
+			IF x # NIL THEN x.Accept(SELF) END;
+		END Type;
+
+		PROCEDURE VisitType(x: SyntaxTree.Type);
+		BEGIN
+		END VisitType;
+
+		PROCEDURE VisitBasicType(x: SyntaxTree.BasicType);
+		BEGIN
+			IF x.typeDeclaration # NIL THEN
+				Identifier(x.typeDeclaration.name)
+			ELSE
+				Identifier(x.name)
+			END
+		END VisitBasicType;
+
+		PROCEDURE VisitBooleanType(x: SyntaxTree.BooleanType);
+		BEGIN VisitBasicType(x)
+		END VisitBooleanType;
+
+		PROCEDURE VisitSetType(x: SyntaxTree.SetType);
+		BEGIN VisitBasicType(x)
+		END VisitSetType;
+
+		PROCEDURE VisitSizeType(x: SyntaxTree.SizeType);
+		BEGIN VisitBasicType(x)
+		END VisitSizeType;
+
+		PROCEDURE VisitCharacterType(x: SyntaxTree.CharacterType);
+		BEGIN VisitBasicType(x)
+		END VisitCharacterType;
+
+		PROCEDURE VisitIntegerType(x: SyntaxTree.IntegerType);
+		BEGIN VisitBasicType(x)
+		END VisitIntegerType;
+
+		PROCEDURE VisitFloatType(x: SyntaxTree.FloatType);
+		BEGIN VisitBasicType(x)
+		END VisitFloatType;
+
+		PROCEDURE VisitComplexType(x: SyntaxTree.ComplexType);
+		BEGIN VisitBasicType(x)
+		END VisitComplexType;
+
+		PROCEDURE VisitByteType(x: SyntaxTree.ByteType);
+		BEGIN VisitBasicType(x)
+		END VisitByteType;
+
+		PROCEDURE VisitQualifiedType(x: SyntaxTree.QualifiedType);
+		BEGIN
+			IF x.qualifiedIdentifier # NIL THEN
+				QualifiedIdentifier(x.qualifiedIdentifier)
+			END;
+		END VisitQualifiedType;
+
+		PROCEDURE VisitStringType(x: SyntaxTree.StringType);
+		BEGIN
+		END VisitStringType;
+
+		PROCEDURE VisitEnumerationType(x: SyntaxTree.EnumerationType);
+		VAR e: SyntaxTree.Constant; first: BOOLEAN;
+		BEGIN
+			Keyword("ENUMERATION"); Whitespace;
+
+			IF x.enumerationBase # NIL THEN
+				String("(");
+				Type(x.enumerationBase);
+				String(")");
+			END;
+			IF ~short THEN
+				e := x.enumerationScope.firstConstant; first := TRUE;
+				WHILE (e # NIL) DO
+					IF ~first THEN String(","); Whitespace; ELSE first := FALSE END;
+					VisitConstant(e);
+					e := e.nextConstant;
+				END;
+			END;
+		END VisitEnumerationType;
+
+		PROCEDURE VisitRangeType(x: SyntaxTree.RangeType);
+		BEGIN VisitBasicType(x);
+		END VisitRangeType;
+
+		PROCEDURE VisitArrayType(x: SyntaxTree.ArrayType);
+		BEGIN
+			Keyword("ARRAY"); Whitespace;
+			IF x.length # NIL THEN Expression(x.length);ToText(w,current.text,ElementType.Default);Whitespace; END;
+			Keyword("OF");Whitespace;
+			Type(x.arrayBase);
+		END VisitArrayType;
+
+		PROCEDURE VisitNilType(x: SyntaxTree.NilType);
+		BEGIN
+			String("NILTYPE");
+		END VisitNilType;
+
+		PROCEDURE VisitAddressType(x: SyntaxTree.AddressType);
+		BEGIN
+			String("ADDRESSTYPE");
+		END VisitAddressType;
+
+		PROCEDURE VisitObjectType(x: SyntaxTree.ObjectType);
+		BEGIN
+			VisitBasicType(x);
+		END VisitObjectType;
+
+		PROCEDURE VisitAnyType(x: SyntaxTree.AnyType);
+		BEGIN
+			VisitBasicType(x);
+		END VisitAnyType;
+
+		PROCEDURE VisitMathArrayType(x: SyntaxTree.MathArrayType);
+		BEGIN
+			Keyword("ARRAY" );Whitespace;
+			IF x.form = SyntaxTree.Tensor THEN String("[?]");
+			ELSE
+				String("[");
+				IF x.length = NIL THEN
+					String("*")
+				ELSE
+					Expression(x.length);
+				END;
+				WHILE(x.arrayBase # NIL) & (x.arrayBase IS SyntaxTree.MathArrayType) DO
+					x := x.arrayBase(SyntaxTree.MathArrayType);
+					String(",");
+					IF x.length = NIL THEN
+						String("*")
+					ELSE
+						Expression(x.length);
+					END;
+				END;
+				String("]");Whitespace;
+			END;
+			IF x.arrayBase # NIL THEN
+				Keyword("OF" );Whitespace;
+				Type(x.arrayBase);
+			END;
+		END VisitMathArrayType;
+
+		PROCEDURE VisitPointerType(x: SyntaxTree.PointerType);
+		VAR pointerBase: SyntaxTree.Type;
+		BEGIN
+			IF x.pointerBase # NIL THEN
+				pointerBase := x.pointerBase;
+				IF (pointerBase IS SyntaxTree.RecordType) & (pointerBase(SyntaxTree.RecordType).isObject) THEN
+					VisitRecordType(pointerBase(SyntaxTree.RecordType))
+				ELSE
+					Keyword("POINTER"); Whitespace; Keyword("TO" ); Whitespace;  Type(x.pointerBase)
+				END;
+			END;
+		END VisitPointerType;
+
+		PROCEDURE VisitPortType(x: SyntaxTree.PortType);
+		BEGIN
+			Keyword("PORT");Whitespace;
+			IF x.direction = SyntaxTree.OutPort THEN
+				Keyword("OUT")
+			ELSE
+				ASSERT(x.direction = SyntaxTree.InPort);
+				Keyword("IN");
+			END;
+			Whitespace;
+			IF x.sizeExpression # NIL THEN
+				String("("); Expression(x.sizeExpression); String(")");
+			END;
+		END VisitPortType;
+
+		PROCEDURE VisitCellType(x: SyntaxTree.CellType);
+		BEGIN
+			Keyword("CELL");Whitespace;
+			Modifiers(x.modifiers);
+			IF x.firstParameter # NIL THEN ParameterList(x.firstParameter) END;
+			IF ~short THEN
+				Summary(current.paragraphs, x.cellScope);
+				Scope(x.cellScope);
+			END;
+		END VisitCellType;
+
+		PROCEDURE VisitRecordType(x: SyntaxTree.RecordType);
+		VAR prevScope: SyntaxTree.Scope; first: BOOLEAN; variable: SyntaxTree.Variable;
+		BEGIN
+			IF x.isObject THEN
+				Keyword("OBJECT");Whitespace;
+				IF x.pointerType # NIL THEN  END;
+				IF (x.baseType # NIL)  THEN
+					String( "(" );
+					IF (x.baseType IS SyntaxTree.RecordType) & (x.baseType(SyntaxTree.RecordType).pointerType # NIL) THEN
+						Type(x.baseType(SyntaxTree.RecordType).pointerType)
+					ELSE
+						Type(x.baseType);
+					END;
+					String( ")" );
+				END;
+				IF ~short THEN
+					Summary(current.paragraphs, x.recordScope);
+					Scope(x.recordScope);
+				END;
+			ELSE
+				Keyword("RECORD");Whitespace;
+				IF (x.baseType # NIL) THEN
+					String( "(" );
+					IF (x.baseType IS SyntaxTree.RecordType) & (x.baseType(SyntaxTree.RecordType).pointerType # NIL) THEN
+						Type(x.baseType(SyntaxTree.RecordType).pointerType)
+					ELSE
+						Type(x.baseType);
+					END;
+					String( ")" );
+				END;
+				IF ~short THEN
+					Summary(current.paragraphs, x.recordScope);
+					Scope(x.recordScope);
+				END;
+			END;
+		END VisitRecordType;
+
+		PROCEDURE VisitProcedureType(x: SyntaxTree.ProcedureType);
+		VAR first: BOOLEAN;
+		BEGIN
+			Keyword("PROCEDURE" );
+			first := TRUE;
+			IF x.isDelegate THEN Flag(Global.NameDelegate,first) END;
+			IF x.isInterrupt THEN Flag(Global.NameInterrupt,first) END;
+			IF x.noPAF THEN Flag(Global.NameNoPAF,first) END;
+			IF x.callingConvention = SyntaxTree.WinAPICallingConvention THEN
+				Flag(Global.NameWinAPI,first)
+			ELSIF x.callingConvention = SyntaxTree.CCallingConvention THEN
+				Flag(Global.NameC,first)
+			END;
+			IF x.stackAlignment > 1 THEN Value(Global.NameStackAligned,x.stackAlignment,first) END;
+			IF ~first THEN String("}") END;
+			Whitespace;
+			IF (x.firstParameter # NIL) OR (x.returnType # NIL) THEN
+				ParameterList(x.firstParameter)
+			END;
+			IF x.returnType # NIL THEN String( ":" );  Whitespace; Type(x.returnType) END;
+		END VisitProcedureType;
+
+		(* expressions *)
+
+		PROCEDURE ExpressionList(x: SyntaxTree.ExpressionList);
+		VAR i: LONGINT;  expression: SyntaxTree.Expression;
+		BEGIN
+			FOR i := 0 TO x.Length() - 1 DO
+				expression := x.GetExpression( i );  Expression(expression);
+				IF i < x.Length() - 1 THEN String( "," );  END;
+			END;
+		END ExpressionList;
+
+		PROCEDURE Expression*(x: SyntaxTree.Expression);
+		BEGIN
+			IF x # NIL THEN
+				x.Accept(SELF);
+			END;
+			w.Update;
+		END Expression;
+
+		PROCEDURE VisitExpression(x: SyntaxTree.Expression);
+		BEGIN
+		END VisitExpression;
+
+		PROCEDURE VisitSet(x: SyntaxTree.Set);
+		BEGIN
+			String( "{" );  ExpressionList(x.elements); String( "}" );
+		END VisitSet;
+
+		PROCEDURE VisitMathArrayExpression(x: SyntaxTree.MathArrayExpression);
+		BEGIN
+			String( "[" );  ExpressionList(x.elements); String( "]" );
+		END VisitMathArrayExpression;
+
+		PROCEDURE VisitUnaryExpression(x: SyntaxTree.UnaryExpression);
+		VAR identifier: SyntaxTree.Identifier;
+		BEGIN
+			IF x.operator = Scanner.Transpose THEN
+				identifier := Global.GetIdentifier(x.operator,case);
+				Expression(x.left);
+				Identifier(identifier);
+			ELSE
+				identifier := Global.GetIdentifier(x.operator,case);
+				Identifier(identifier);
+				Expression(x.left);
+			END;
+		END VisitUnaryExpression;
+
+		PROCEDURE VisitBinaryExpression(x: SyntaxTree.BinaryExpression);
+		VAR identifier: SyntaxTree.Identifier;
+		BEGIN
+			String( "(" );
+			Expression(x.left);
+			identifier := Global.GetIdentifier(x.operator,case);
+			Identifier(identifier);
+			Expression(x.right);
+			String(")");
+		END VisitBinaryExpression;
+
+		PROCEDURE VisitRangeExpression(x: SyntaxTree.RangeExpression);
+		BEGIN
+			IF x.missingFirst & x.missingLast & x.missingStep THEN
+				String("*")
+			ELSE
+				IF ~x.missingFirst THEN Expression(x.first) END;
+				String("..");
+				IF ~x.missingLast THEN Expression(x.last) END;
+				IF ~x.missingStep THEN
+					Keyword("BY");
+					Expression(x.step)
+				END
+			END;
+		END VisitRangeExpression;
+
+		PROCEDURE VisitTensorRangeExpression(x: SyntaxTree.TensorRangeExpression);
+		BEGIN String("?");
+		END VisitTensorRangeExpression;
+
+		PROCEDURE VisitConversion(x: SyntaxTree.Conversion);
+		BEGIN
+			IF x.typeExpression # NIL THEN Expression(x.typeExpression); String("(");
+			END;
+			Expression(x.expression);
+			IF x.typeExpression # NIL THEN String(")") END;
+		END VisitConversion;
+
+		PROCEDURE VisitSymbolDesignator(x: SyntaxTree.SymbolDesignator);
+		BEGIN
+			IF x.left # NIL THEN
+			Expression(x.left); String(".");
+			END;
+			IF x.symbol IS SyntaxTree.Operator THEN
+				String('"'); Identifier(x.symbol.name); String('"');
+			ELSE
+				Identifier(x.symbol.name)
+			END;
+		END VisitSymbolDesignator;
+
+		PROCEDURE VisitBuiltinCallDesignator(x: SyntaxTree.BuiltinCallDesignator);
+		BEGIN
+			IF x.left # NIL THEN
+				Expression(x.left);
+			ELSE
+				String("BUILTIN(");
+				w.Int(x.id,1);
+				String(")");
+			END;
+			String("("); ExpressionList(x.parameters); String(")");
+		END VisitBuiltinCallDesignator;
+
+		PROCEDURE VisitValue(x: SyntaxTree.Value);
+		BEGIN
+		END VisitValue;
+
+		PROCEDURE VisitBooleanValue(x: SyntaxTree.BooleanValue);
+		BEGIN
+			IF Scanner.Uppercase = case THEN
+				IF x.value THEN String("TRUE" ) ELSE String( "FALSE" ) END
+			ELSE
+				IF x.value THEN String("true" ) ELSE String( "false" ) END
+			END
+		END VisitBooleanValue;
+
+		PROCEDURE VisitIntegerValue(x: SyntaxTree.IntegerValue);
+
+			PROCEDURE InBounds(val: HUGEINT; bits: LONGINT): BOOLEAN;
+			VAR m: HUGEINT;
+			BEGIN
+				m := Runtime.AslH(1,bits-1);
+				RETURN (val < m) & (-val <= m)
+			END InBounds;
+		BEGIN
+			(*! use subtype for representation form ?  *)
+			IF x.hvalue = MIN(HUGEINT) THEN
+				(* special case: display 8000000000000000H without leading minus sign
+					to avoid double minus sign for unary expression -8000000000000000H
+				*)
+				w.Char("0"); w.Hex(x.hvalue,-16); w.Char("H");
+			ELSIF InBounds(x.hvalue,32) THEN
+				Int(SHORT(x.hvalue));
+			ELSE
+				Hex(w,x.hvalue); w.Char("H");
+			END;
+			ToText(w,current.text,ElementType.Default);
+		END VisitIntegerValue;
+
+		PROCEDURE VisitCharacterValue(x: SyntaxTree.CharacterValue);
+		BEGIN
+			Hex(w, ORD(x.value));  w.String( "X" );
+			ToText(w,current.text,ElementType.Default);
+		END VisitCharacterValue;
+
+		PROCEDURE VisitSetValue(x: SyntaxTree.SetValue);
+		VAR i: LONGINT;
+		BEGIN
+			w.String("{");
+			i := 0;
+			WHILE (i<MAX(SET)) & ~(i IN x.value)  DO
+				INC(i);
+			END;
+			IF i<MAX(SET) THEN
+				w.Int(i,1);
+				INC(i);
+				WHILE i < MAX(SET) DO
+					IF i IN x.value THEN w.String(","); w.Int(i,1); END;
+					INC(i)
+				END
+			END;
+			w.String("}");
+			ToText(w,current.text,ElementType.Default);
+		END VisitSetValue;
+
+		PROCEDURE VisitMathArrayValue(x: SyntaxTree.MathArrayValue);
+		BEGIN
+			VisitMathArrayExpression(x.array);
+		END VisitMathArrayValue;
+
+		PROCEDURE FormatedFloat(value: LONGREAL; subtype: LONGINT);
+		VAR string: ARRAY 128 OF CHAR; i: LONGINT;
+		BEGIN
+			IF subtype = Scanner.Real THEN
+				ws.SetPos(0); ws.Float(value,11(*mantissa X.XXXXXXX *)+5(*exponent E+XXX *)); ws.Get(string);
+				i := 0;
+				WHILE(i<LEN(string)) & (string[i] # 0X) DO
+					IF string[i] = "D" THEN string[i] := "E" END;
+					INC(i);
+				END;
+				String(string);
+			ELSIF subtype = Scanner.Longreal THEN
+				ws.SetPos(0); ws.Float(value,20(*mantissa X.X..(16)..X *)+5(*exponent E+XXX *) ); ws.Get(string);
+				i := 0;
+				WHILE(i<LEN(string)) & (string[i] # 0X) DO
+					IF string[i] = "E" THEN string[i] := "D" END;
+					INC(i);
+				END;
+				String(string);
+			ELSE
+				w.Float(value,64);
+				ToText(w,current.text,ElementType.Default);
+			END;
+		END FormatedFloat;
+
+		PROCEDURE VisitRealValue(x: SyntaxTree.RealValue);
+		BEGIN FormatedFloat(x.value, x.subtype)
+		END VisitRealValue;
+
+		PROCEDURE VisitComplexValue(x: SyntaxTree.ComplexValue);
+		BEGIN
+			IF (x.realValue = 0) & (x.imagValue = 1) THEN
+				String("IMAG")
+			ELSE
+				String("(");
+				FormatedFloat(x.realValue, x.subtype)	;
+				IF x.imagValue > 0 THEN String("+") END;
+				FormatedFloat(x.imagValue, x.subtype);
+				String("*IMAG)")
+			END
+		END VisitComplexValue;
+
+		PROCEDURE VisitStringValue(x: SyntaxTree.StringValue);
+		VAR i: LONGINT;
+		BEGIN
+			i := 0;
+			WHILE (i < LEN( x.value )) & (x.value[i] # 0X) & (x.value[i] # '"') DO INC( i );  END;
+			ASSERT(i # LEN(x.value));
+			IF  (x.value[i] = 0X) THEN (* no double quotes contained in the string *)
+				w.String( '"' );  w.String( x.value^ );  w.String( '"' );
+			ELSE  (* double quotes found in string *)
+				w.String( "'" );  w.String( x.value^ );  w.String( "'" );
+			END;
+			ToText(w,current.text,ElementType.Default);
+		END VisitStringValue;
+
+		PROCEDURE VisitNilValue(x: SyntaxTree.NilValue);
+		BEGIN String( "NIL" );
+		END VisitNilValue;
+
+		PROCEDURE VisitEnumerationValue(x: SyntaxTree.EnumerationValue);
+		BEGIN w.Int(x.value,1); ToText(w,current.text,ElementType.Default);
+		END VisitEnumerationValue;
+
+		(**** symbols ****)
+
+		PROCEDURE VisitParameter(x: SyntaxTree.Parameter);
+		END VisitParameter;
+
+		PROCEDURE PrintSymbol(x: SyntaxTree.Symbol);
+		VAR first: BOOLEAN; w: Streams.StringWriter; name: Scanner.IdentifierString;
+		BEGIN
+			NEW(w,256);
+
+			Basic.GetString(x.name,name);
+			IF x IS SyntaxTree.Operator THEN
+				w.String('"'); w.String(name); w.String('"')
+			ELSE
+				w.String(name)
+			END;
+			IF SyntaxTree.PublicWrite IN x.access THEN w.String( "*" )
+			ELSIF SyntaxTree.PublicRead IN x.access THEN
+				IF x IS SyntaxTree.Variable THEN
+					w.String( "-" )
+				ELSIF ~(x IS SyntaxTree.Parameter) THEN
+					w.String("*")
+				END
+			END;
+			ToText(w, current.text,ElementType.Default);
+		END PrintSymbol;
+
+		PROCEDURE ParameterList*(x: SyntaxTree.Parameter);
+		VAR next: SyntaxTree.Parameter; first: BOOLEAN;
+		BEGIN
+			first := TRUE;
+			String( "(" );
+			WHILE(x # NIL) DO
+				next := x.nextParameter;
+				IF (x.access # SyntaxTree.Hidden) THEN
+					IF ~first THEN String(";"); Whitespace END;
+					first := FALSE;
+					IF x.kind = SyntaxTree.VarParameter THEN Keyword("VAR" );Whitespace;
+					ELSIF x.kind = SyntaxTree.ConstParameter THEN Keyword("CONST" );Whitespace;
+					END;
+					PrintSymbol(x);
+					IF x.defaultValue # NIL THEN
+						String("="); Whitespace; Expression(x.defaultValue);
+					END;
+
+					WHILE (next # NIL) & (next.type = x.type) & (next.kind = x.kind) & ((next.access # SyntaxTree.Hidden) ) DO
+						String(",");Whitespace;
+						PrintSymbol(next);
+						IF next.defaultValue # NIL THEN
+							String("="); Whitespace; Expression(next.defaultValue);
+						END;
+						next := next.nextParameter;
+					END;
+					IF x.access # SyntaxTree.Hidden THEN
+						String(":");Whitespace;
+						Type(x.type);
+					ELSE
+						String(":");Whitespace;
+						Type(x.type);
+					END;
+				END;
+				x := next;
+			END;
+			String( ")" );
+		END ParameterList;
+
+		PROCEDURE Visible(symbol: SyntaxTree.Symbol): BOOLEAN;
+		BEGIN
+			RETURN (symbol # NIL) & ( (SyntaxTree.Public * symbol.access # {}) OR backend.internals)
+		END Visible;
+
+		PROCEDURE Symbol*(x: SyntaxTree.Symbol);
+		BEGIN
+			IF Visible(x) THEN x.Accept(SELF) END
+		END Symbol;
+
+		PROCEDURE NeedsSection(x: SyntaxTree.Symbol): BOOLEAN;
+		VAR declaredType: SyntaxTree.Type;tmp: SyntaxTree.Comment;
+		BEGIN
+			IF x.comment # NIL THEN
+				tmp := x.comment;
+				WHILE (tmp # NIL) & (tmp.item = x) DO
+					IF tmp.source[0] = "*" THEN RETURN TRUE END;
+					tmp := tmp.nextComment
+				END;
+			END;
+			IF x IS SyntaxTree.TypeDeclaration THEN
+				declaredType := x(SyntaxTree.TypeDeclaration).declaredType.resolved;
+				IF declaredType IS SyntaxTree.PointerType THEN declaredType := declaredType(SyntaxTree.PointerType).pointerBase.resolved END;
+				RETURN (declaredType IS SyntaxTree.RecordType) OR (declaredType IS SyntaxTree.EnumerationType)
+			END;
+			RETURN FALSE
+		END NeedsSection;
+
+
+		PROCEDURE ExtractParameters(x: SyntaxTree.ProcedureType; doc: DocumentationTree.Document; VAR parameters: DocumentationTree.Document);
+		VAR par: SyntaxTree.Parameter; paragraph,heading: DocumentationTree.Paragraph; i: LONGINT; element: DocumentationTree.TextElement;
+			string: DocumentationTree.String; id: SyntaxTree.Identifier; done: BOOLEAN;
+		BEGIN
+			i := 0;
+			WHILE i < doc.description.Length() DO
+				paragraph := doc.description.GetParagraph(i);
+				done := FALSE;
+				IF (paragraph.type = ParagraphType.Description) & (paragraph.description.Length() = 1) THEN
+					element := paragraph.description.GetElement(0);
+					string := element.string;
+					id := SyntaxTree.NewIdentifier(string^);
+					par := x.firstParameter;
+					WHILE (par # NIL) & ~done DO
+						IF par.name = id THEN
+							done := TRUE;
+							doc.description.RemoveByIndex(i);
+							IF parameters = NIL THEN
+								NEW(parameters);
+							END;
+							parameters.description.Add(paragraph);
+						END;
+						par := par.nextParameter;
+					END;
+
+					IF ~done & ((string^="RESULT") OR (string^="result")) THEN
+						doc.description.RemoveByIndex(i);
+						IF parameters = NIL THEN
+							NEW(parameters);
+						END;
+						parameters.description.Add(paragraph);
+						done := TRUE;
+					END;
+
+				END;
+				IF ~done THEN INC(i) END;
+			END;
+		END ExtractParameters;
+
+
+		PROCEDURE WriteSymbolSection(x: SyntaxTree.Symbol);
+		VAR section: DocumentationTree.Section; paragraph: DocumentationTree.Paragraph; commentDoc: DocumentationTree.Document;
+			parameters: DocumentationTree.Document;
+		BEGIN
+			IF Visible(x) & NeedsSection(x) THEN
+				section := BeginSymbolSection("",x);
+
+				parameterDocument := NIL;
+
+
+				IF x.comment # NIL THEN
+					commentDoc := DocumentationTree.NewDocument();
+					ParseComments(commentDoc,x.comment, NIL,x);
+					PatchLinks(commentDoc, x.scope);
+					KeepSections(commentDoc);
+					IF (x IS SyntaxTree.Procedure) THEN
+						ExtractParameters(x.type(SyntaxTree.ProcedureType), commentDoc, parameters);
+					END;
+					MergeDocument(current.document, section.contents, commentDoc);
+				END;
+
+				paragraph := section.contents.AppendNew(ParagraphType.Heading);
+				paragraph.SetLevel(2);
+				paragraph.text.WriteString("Syntax");
+
+				paragraph := section.contents.AppendNew(ParagraphType.Code);
+				current.text := paragraph.text;
+				Symbol(x);
+
+				IF parameters # NIL THEN
+					paragraph := section.contents.AppendNew(ParagraphType.Heading);
+					paragraph.SetLevel(2);
+					paragraph.text.WriteString("Parameters");
+					MergeDocument(current.document, section.contents, parameters);
+				END;
+
+				EndSection(section);
+			END;
+		END WriteSymbolSection;
+
+		PROCEDURE BeginSymbolSection(CONST title: ARRAY OF CHAR; x: SyntaxTree.Symbol):DocumentationTree.Section;
+		VAR section: DocumentationTree.Section; name: Basic.SectionName; paragraph:DocumentationTree.Paragraph;
+		BEGIN
+			section := BeginSection(current.document);
+			current.section := section;
+			current.paragraphs := section.contents;
+			WriteSymbolLabel(section.title, x);
+			(*
+			Global.GetSymbolNameInScope(x,(* current.scope*) NIL ,name);
+			*)
+			WriteReferenceInScope(section.title, x, NIL);
+			(*
+			section.title.WriteString(name); (* WriteString(section.title,title);*)
+			*)
+			(*
+			section := current.section;
+			current.paragraphs := section.contents;
+			paragraph := section.contents.AppendNew(ParagraphType.Line);
+			paragraph := section.contents.AppendNew(ParagraphType.Heading);
+			paragraph.SetLevel(1);
+			WriteSymbolLabel(paragraph.text, x);
+			Global.GetSymbolNameInScope(x,(* current.scope*) NIL ,name);
+			WriteString(paragraph.text, name); (* WriteString(section.title,title);*)
+			*)
+			RETURN section;
+		END BeginSymbolSection;
+
+		PROCEDURE VisitSymbol(x: SyntaxTree.Symbol);
+		BEGIN
+		END VisitSymbol;
+
+		PROCEDURE VisitTypeDeclaration(x: SyntaxTree.TypeDeclaration);
+		VAR section: DocumentationTree.Section; paragraph: DocumentationTree.Paragraph;commentDoc: DocumentationTree.Document;
+		BEGIN
+			IF ~short THEN
+				Keyword("TYPE"); Whitespace;
+				PrintSymbol(x);
+			ELSE
+				WriteSymbolReference(current.text, x, current.scope);
+			END;
+			Whitespace;
+			String("="); Type(x.declaredType);
+		END VisitTypeDeclaration;
+
+		PROCEDURE VisitConstant(x: SyntaxTree.Constant);
+		VAR section: DocumentationTree.Section;paragraph: DocumentationTree.Paragraph; commentDoc: DocumentationTree.Document;
+		BEGIN
+			IF ~short THEN
+				Keyword("CONST"); Whitespace; PrintSymbol(x)
+			ELSE
+				WriteSymbolReference(current.text, x, current.scope);
+			END;
+			IF x.value # NIL THEN
+				String( "=" );  Whitespace; Expression(x.value);
+			END;
+		END VisitConstant;
+
+		PROCEDURE VisitVariable(x: SyntaxTree.Variable);
+		VAR section: DocumentationTree.Section; paragraph: DocumentationTree.Paragraph;commentDoc: DocumentationTree.Document;
+		BEGIN
+			IF ~short THEN
+				Keyword("VAR"); Whitespace; PrintSymbol(x)
+			ELSE
+				WriteSymbolReference(current.text, x, current.scope);
+			END;
+			String( ":" );Whitespace;
+			Type(x.type);
+		END VisitVariable;
+
+		PROCEDURE Flag(identifier: SyntaxTree.Identifier; VAR first: BOOLEAN);
+		VAR name: SyntaxTree.IdentifierString;
+		BEGIN
+			IF first THEN String("{") ELSE String(",") END;
+			first := FALSE;
+			Basic.GetString(identifier,name);
+			String(name);
+		END Flag;
+
+		PROCEDURE FlagEnd(first: BOOLEAN);
+		BEGIN
+			IF ~first THEN String("}") END;
+		END FlagEnd;
+
+		PROCEDURE Int(value: LONGINT);
+		VAR s: DocumentationTree.String; textElement: DocumentationTree.TextElement;
+		BEGIN
+			NEW(s,32); Strings.IntToStr(value,s^);
+			textElement := current.text.AppendNew(ElementType.Default);
+			textElement.SetString(s);
+		END Int;
+
+		PROCEDURE Value(identifier: SyntaxTree.Identifier; value: LONGINT; VAR first: BOOLEAN);
+		BEGIN
+			Flag(identifier,first);
+			w.String("("); w.Int(value,1); w.String(")");ToText(w,current.text,ElementType.Default);
+		END Value;
+
+		(** process procedure including comments describing the procedure *)
+		PROCEDURE VisitProcedure(x: SyntaxTree.Procedure);
+		VAR section: DocumentationTree.Section; paragraph: DocumentationTree.Paragraph; name: Basic.SectionName; first: BOOLEAN; type: SyntaxTree.ProcedureType;
+		doc: DocumentationTree.Document; par: SyntaxTree.Parameter;
+		BEGIN
+			IF ~short  THEN
+				IF x IS SyntaxTree.Operator THEN
+					Keyword("OPERATOR")
+				ELSE
+					Keyword("PROCEDURE")
+				END;
+			END;
+
+			IF x.isInline THEN String("-") END;
+			IF x.isConstructor THEN String("&") END;
+			Whitespace;
+
+			type := x.type(SyntaxTree.ProcedureType);
+			first := TRUE;
+			IF type.stackAlignment > 1 THEN Value(Global.NameStackAligned,type.stackAlignment,first) END;
+			IF (type.isRealtime) THEN Flag(Global.NameRealtime,first) END;
+			IF (x.fixed) THEN Value(Global.NameFixed, x.alignment,first)
+			ELSIF (x.alignment >1) THEN Value(Global.NameAligned, x.alignment, first)
+			END;
+			FlagEnd(first);
+
+			IF ~short THEN
+				PrintSymbol(x)
+			ELSE
+				WriteSymbolReference(current.text, x, current.scope);
+			END;
+
+			IF (type.firstParameter # NIL)  OR (type.returnType # NIL ) THEN  (* print parentheses only if not parameterless procedure *)
+				Whitespace;
+				ParameterList(type.firstParameter);
+			END;
+			IF type.returnType # NIL THEN
+				String( ":" );
+				Whitespace;
+				Type(type.returnType);
+			END;
+		END VisitProcedure;
+
+		PROCEDURE String(CONST name: ARRAY OF CHAR);
+		BEGIN current.text.WriteString(name);
+		END String;
+
+		PROCEDURE Whitespace;
+		BEGIN
+			current.text.WriteWhitespace
+		END Whitespace;
+
+
+		PROCEDURE VisitOperator(x: SyntaxTree.Operator);
+		BEGIN VisitProcedure(x);
+		END VisitOperator;
+
+		PROCEDURE VisitImport(x: SyntaxTree.Import);
+		VAR context: SyntaxTree.Identifier; name: Basic.SectionName; 
+		BEGIN
+			x.GetName(name);
+			current.text.WriteLabel(name);
+			IF x.moduleName # x.name THEN PrintSymbol(x); String( " := " );  END;
+			IF (x.scope = NIL) OR (x.scope.ownerModule = NIL) THEN context := SyntaxTree.invalidIdentifier ELSE context := x.scope.ownerModule.context END;
+			WriteReferenceInScope(current.text, x.module, NIL);
+			IF (x.context # SyntaxTree.invalidIdentifier) & (x.context#context) THEN
+				String(" IN ");
+				Identifier(x.context)
+			END;
+		END VisitImport;
+
+		PROCEDURE VisitBuiltin(x: SyntaxTree.Builtin);
+		BEGIN
+		END VisitBuiltin;
+
+		(*** scopes ****)
+
+		PROCEDURE Scope*(x: SyntaxTree.Scope);
+		VAR
+			constant: SyntaxTree.Constant;
+			type: SyntaxTree.TypeDeclaration;
+			variable : SyntaxTree.Variable;
+			procedure: SyntaxTree.Procedure;
+			prevScope: SyntaxTree.Scope;
+		BEGIN
+			prevScope := current.scope;
+			current.scope := x;
+
+			constant := x.firstConstant;
+			WHILE constant # NIL DO WriteSymbolSection(constant); constant := constant.nextConstant; END;
+
+			type := x.firstTypeDeclaration;
+			WHILE type # NIL DO WriteSymbolSection(type); type := type.nextTypeDeclaration END;
+
+			variable := x.firstVariable;
+			WHILE variable # NIL DO WriteSymbolSection(variable); variable := variable.nextVariable END;
+
+			procedure := x.firstProcedure;
+			WHILE procedure # NIL DO WriteSymbolSection(procedure); procedure := procedure.nextProcedure END;
+
+			current.scope := prevScope;
+		END Scope;
+		PROCEDURE SymbolRow(CONST head: ARRAY OF CHAR; symbol: SyntaxTree.Symbol; VAR first: BOOLEAN);
+		VAR row, cell: DocumentationTree.TextElement; x: SyntaxTree.Constant;
+		BEGIN
+			IF Visible(symbol) THEN
+				row := current.text.AppendNew(ElementType.Row);
+				IF first THEN
+					first := FALSE;
+					cell := row.text.AppendNew(ElementType.HeaderCell);
+					cell.text.WriteString(head);
+				ELSE
+					cell := row.text.AppendNew(ElementType.DataCell);
+				END;
+				cell := row.text.AppendNew(ElementType.DataCell);
+				current.text := cell.text;
+				Symbol(symbol);
+			END;
+		END SymbolRow;
+
+		PROCEDURE Summary(paragraphs: DocumentationTree.Paragraphs; x: SyntaxTree.Scope);
+		VAR
+			import: SyntaxTree.Import;
+			constant: SyntaxTree.Constant;
+			type: SyntaxTree.TypeDeclaration;
+			variable : SyntaxTree.Variable;
+			procedure: SyntaxTree.Procedure;
+			first: BOOLEAN;
+			prevScope: SyntaxTree.Scope;
+
+			paragraph: DocumentationTree.Paragraph;
+
+
+		BEGIN
+			prevScope := current.scope;
+			current.scope := x;
+
+			paragraph := paragraphs.AppendNew(ParagraphType.Heading);	paragraph.SetLevel(2); paragraph.text.WriteString("Summary");
+
+			paragraph := paragraphs.AppendNew(ParagraphType.Table);
+			current.text := paragraph.text;
+
+
+			short := TRUE;
+
+			IF x IS SyntaxTree.ModuleScope THEN
+				import := x(SyntaxTree.ModuleScope).firstImport; first := TRUE;
+				WHILE import # NIL DO SymbolRow("imports",import,first); import := import.nextImport; END;
+			END;
+
+			constant := x.firstConstant; first := TRUE;
+			WHILE constant # NIL DO SymbolRow("constants",constant,first); constant := constant.nextConstant; END;
+
+			type := x.firstTypeDeclaration; first := TRUE;
+			WHILE type # NIL DO SymbolRow("type declarations", type,first); type := type.nextTypeDeclaration END;
+
+			variable := x.firstVariable; first := TRUE;
+			WHILE variable # NIL DO SymbolRow("variables", variable,first); variable := variable.nextVariable END;
+
+			procedure := x.firstProcedure; first := TRUE;
+			WHILE procedure # NIL DO SymbolRow("procedures", procedure,first); procedure := procedure.nextProcedure END;
+
+			current.scope := prevScope;
+			short := FALSE;
+
+			(*
+			paragraph := paragraphs.AppendNew(ParagraphType.TextBlock);
+			paragraph.text.WriteLink("ModuleList","List of modules");
+			*)
+		END Summary;
+
+		PROCEDURE WriteTextElement(textElement: DocumentationTree.TextElement);
+		VAR paragraph: DocumentationTree.Paragraph; element: DocumentationTree.TextElement;
+		BEGIN
+			ASSERT(current.text # NIL);
+			CASE textElement.type OF
+			ElementType.Italic, ElementType.Bold, ElementType.Underline, ElementType.Code: current.text.Add(textElement);
+				current.text := textElement.text;
+			| ElementType.HeaderCell, ElementType.DataCell, ElementType.Row:
+				ASSERT(current.paragraphs # NIL);
+				ASSERT(current.paragraphs.Length() # 0);
+				paragraph := current.paragraphs.Last();
+				current.text := paragraph.text;
+				IF textElement.type # ElementType.Row THEN
+					ASSERT(current.text.Length() # 0);
+					element := current.text.Last();
+					current.text := element.text
+				END;
+				current.text.Add(textElement);
+				current.text := textElement.text;
+			ELSE
+				current.text.Add(textElement)
+			END;
+		END WriteTextElement;
+
+		PROCEDURE BeginSection(document: DocumentationTree.Document): DocumentationTree.Section;
+		VAR section: DocumentationTree.Section;
+		BEGIN
+			INC(level[Section]);
+			section := document.sections.AppendNew(level[Section]);
+			RETURN section;
+		END BeginSection;
+
+		PROCEDURE PrefixSection(document: DocumentationTree.Document): DocumentationTree.Section;
+		VAR section: DocumentationTree.Section;
+		BEGIN
+			INC(level[Section]);
+			NEW(section, level[Section]);
+			document.sections.Insert(0, section);
+			RETURN section;
+		END PrefixSection;
+
+		PROCEDURE EndSection(section: DocumentationTree.Section);
+		BEGIN
+			DEC(level[Section]);
+		END EndSection;
+
+		PROCEDURE PatchLinkE(element: DocumentationTree.TextElement);
+		VAR label: Strings.String; symbol: SyntaxTree.Symbol;name: Basic.SectionName; identifier: SyntaxTree.Identifier;
+		BEGIN
+			IF (element.type = ElementType.Link) & (element.string # NIL) THEN
+				identifier := SyntaxTree.NewIdentifier(element.string^);
+				symbol := current.scope.FindSymbol(identifier);
+				IF (symbol # NIL) & ~(symbol IS SyntaxTree.Import) THEN
+					IF element.text.Length()= 0 THEN element.text.WriteString(element.string^) END;
+					Global.GetSymbolNameInScope(symbol, NIL, name);
+					element.SetString(Strings.NewString(name));
+				END;
+			END;
+			element.text.ForAllElementsDo(PatchLinkE)
+		END PatchLinkE;
+
+		PROCEDURE PatchLinksP(par: DocumentationTree.Paragraph);
+		BEGIN
+			par.text.ForAllElementsDo(PatchLinkE)
+		END PatchLinksP;
+
+		PROCEDURE PatchLinksS(sec: DocumentationTree.Section);
+		VAR i: LONGINT;
+		BEGIN
+			sec.title.ForAllElementsDo(PatchLinkE);
+			sec.contents.ForAllParagraphsDo(PatchLinksP)
+		END PatchLinksS;
+
+		PROCEDURE PatchLinks(doc: DocumentationTree.Document; scope: SyntaxTree.Scope);
+		VAR i: LONGINT; prev: SyntaxTree.Scope;
+		BEGIN
+			prev := current.scope;
+			current.scope := scope;
+			doc.sections.ForAllSectionsDo(PatchLinksS);
+			doc.description.ForAllParagraphsDo(PatchLinksP);
+			current.scope := prev
+		END PatchLinks;
+		
+		PROCEDURE Modifiers(x: SyntaxTree.Modifier);
+		VAR name: Scanner.IdentifierString; first: BOOLEAN;
+		BEGIN
+			first := TRUE;
+			WHILE x # NIL DO
+				IF first THEN String("{"); first := FALSE ELSE String(", ") END;
+				Basic.GetString(x.identifier,name);
+				String(name);
+				IF x.expression # NIL THEN
+					String("(");
+					Expression(x.expression);
+					String(")");
+				END;
+				x := x.nextModifier;
+			END;
+			IF ~first THEN String("} ") END;
+		END Modifiers;
+
+		PROCEDURE Module*(x: SyntaxTree.Module; backend: DocumentationBackend): DocumentationTree.Document;
+		VAR
+			name: SyntaxTree.IdentifierString; section, cs: DocumentationTree.Section; commentDoc: DocumentationTree.Document;
+			doc: DocumentationTree.Document; par: DocumentationTree.Paragraph; text: DocumentationTree.Text;
+		BEGIN
+			SELF.backend := backend;
+			ASSERT(x # NIL);
+			doc := DocumentationTree.NewDocument();
+			level[Section] := 0;
+
+			Global.GetModuleName(x,name);
+
+			section := BeginSection(doc);
+			section.WriteLabel("ModuleList");
+			par := section.contents.AppendNew(ParagraphType.TextBlock);
+			par.text.WriteString("Module");
+			par.text.WriteWhitespace;
+			par.text.WriteLink(name, name);
+
+			EndSection(section);
+
+			section := BeginSection(doc);
+			section.title.WriteLabel(name);
+			section.title.WriteWeakLink("ModuleList","Module"); (* if there is a module list, then e refer to it here *)
+			section.title.WriteWhitespace;
+			section.title.WriteString(name);
+
+			IF x.comment # NIL THEN
+				commentDoc := DocumentationTree.NewDocument();
+				ParseComments(commentDoc,x.comment, x.closingComment,x);
+				PatchLinks(commentDoc, x.moduleScope);
+				MergeDocument(doc, section.contents, commentDoc);
+			END;
+
+			Summary(section.contents, x.moduleScope);
+
+			EndSection(section);
+
+			IF x.closingComment # NIL THEN
+				cs := BeginSection(doc);
+				commentDoc := DocumentationTree.NewDocument();
+				ParseComments(commentDoc,x.closingComment, NIL,x);
+				PatchLinks(commentDoc, x.moduleScope);
+				MergeDocument(doc, cs.contents, commentDoc);
+				EndSection(cs);
+			END;
+
+			current.document := doc;
+
+			INC(level[Section]);
+
+			Scope(x.moduleScope);
+
+			DEC(level[Section]);
+
+			current.scope := x.moduleScope;
+
+			MergeDocument(doc, doc.description, document);
+			document := doc;
+			RETURN document
+		END Module;
+
+	END Generator;
+
+	Checker= OBJECT
+	VAR
+		labels: DocumentationTree.Text;
+		links: DocumentationTree.Text;
+		currentScope: SyntaxTree.Scope;
+
+		PROCEDURE CheckElement(element: DocumentationTree.TextElement);
+		VAR label: Strings.String; symbol: SyntaxTree.Symbol;name: Basic.SectionName; identifier: SyntaxTree.Identifier;
+		BEGIN
+			IF (element.type = ElementType.Link) & (element.string # NIL) THEN
+				identifier := SyntaxTree.NewIdentifier(element.string^);
+				symbol := currentScope.FindSymbol(identifier);
+				IF symbol # NIL THEN
+					Global.GetSymbolNameInScope(symbol, NIL, name);
+					element.SetString(Strings.NewString(name));
+				END;
+			ELSIF (element.type = ElementType.WeakLink) THEN
+				IF labels.FindByString(element.string^) = NIL THEN
+					element.SetType(ElementType.Default)
+				ELSE
+					element.SetType(ElementType.Link)
+				END;
+			END;
+		END CheckElement;
+
+		PROCEDURE CollectElement(element: DocumentationTree.TextElement);
+		BEGIN
+			IF (element.type = ElementType.WeakLink) THEN
+				links.AddElement(element);
+			ELSIF (element.type = ElementType.Label) THEN
+				labels.Add(element)
+			END;
+			element.text.ForAllElementsDo(CollectElement);
+		END CollectElement;
+
+		PROCEDURE CollectParagraph(par: DocumentationTree.Paragraph);
+		BEGIN
+			IF par.label # NIL THEN
+				labels.WriteLabel(par.label^)
+			END;
+			par.text.ForAllElementsDo(CollectElement);
+			par.description.ForAllElementsDo(CollectElement);
+		END CollectParagraph;
+
+		PROCEDURE CollectSection(sec: DocumentationTree.Section);
+		BEGIN
+			IF sec.label # NIL THEN
+				labels.WriteLabel(sec.label^)
+			END;
+			sec.title.ForAllElementsDo(CollectElement);
+			sec.contents.ForAllParagraphsDo(CollectParagraph);
+		END CollectSection;
+
+		PROCEDURE Document(doc: DocumentationTree.Document; scope: SyntaxTree.Scope);
+		BEGIN
+			currentScope := scope;
+			NEW(links, 4); NEW(labels, 4);
+			doc.description.ForAllParagraphsDo(CollectParagraph);
+			doc.sections.ForAllSectionsDo(CollectSection);
+			links.ForAllElementsDo(CheckElement)
+		END Document;
+
+	END Checker;
+
+	DocumentationBackend= OBJECT (Backend.Backend)
+	VAR
+		trace: BOOLEAN;
+		fileName : Files.FileName;
+		generator: Generator;
+		templateFile: Files.FileName;
+		internals: BOOLEAN;
+
+		PROCEDURE &InitIntermediateBackend*;
+		BEGIN
+			InitBackend; generator := NIL;
+		END InitIntermediateBackend;
+
+		PROCEDURE ParseFile(fileName: ARRAY OF CHAR): DocumentationTree.Document;
+		VAR reader: Files.Reader; parser: DocumentationParser.Parser; scanner: DocumentationScanner.Scanner;
+			printer: DocumentationPrinter.Printer; document: DocumentationTree.Document;
+			file: Files.File;
+		BEGIN
+			file := Files.Old(fileName);
+			IF file = NIL THEN RETURN NIL END;
+			NEW(reader, file, 0);
+			NEW(scanner, reader,0,NIL);
+			NEW(parser, scanner);
+			document := DocumentationTree.NewDocument();
+			parser.ParseDocument(document);
+			
+			RETURN document
+		END ParseFile;
+
+		PROCEDURE ProcessSyntaxTreeModule(syntaxTreeModule: SyntaxTree.Module): Formats.GeneratedModule;
+		VAR dump: Streams.Writer;  printer: DocumentationPrinter.Printer; document, template: DocumentationTree.Document;
+			htmlPrinter: DocumentationHtml.Printer; file: Files.File; writer: Files.Writer;
+
+			name, extension: Files.FileName; checker: Checker;
+		BEGIN
+			Files.SplitExtension(fileName, name, extension);
+
+			IF (name = "*") OR (generator = NIL) THEN
+				NEW(generator,diagnostics);
+				generator.case := syntaxTreeModule.case;
+			END;
+			document := generator.Module(syntaxTreeModule,SELF);
+			IF templateFile # "" THEN
+				template := ParseFile(templateFile);
+				IF template # NIL THEN
+					MergeDocument(template, NIL, document);
+					document := template;
+				ELSIF diagnostics # NIL THEN
+					diagnostics.Error("",Diagnostics.Invalid, Diagnostics.Invalid,"could not  open / parse documentation template");
+				END;
+			END;
+
+			NEW(checker);
+			checker.Document(document, NIL);
+
+			IF trace THEN
+				Global.GetModuleName(syntaxTreeModule, name);
+				Strings.Append(name,".docu");
+				dump := Basic.GetDebugWriter(name);
+				NEW(printer, dump);
+				printer.Document(document);
+				NEW(htmlPrinter, dump);
+				htmlPrinter.Document(document);
+				dump.Update;
+			END;
+			IF name = "*" THEN
+				Global.GetModuleName(syntaxTreeModule, name);
+			END;
+			Files.JoinExtension(name, extension, name);
+			file := Files.New(name);
+			IF file # NIL THEN
+				NEW(writer, file,0);
+				IF extension = "html" THEN
+					NEW(htmlPrinter, writer);
+					htmlPrinter.Document(document);
+				ELSE
+					NEW(printer, writer);
+					printer.Document(document);
+				END;
+				writer.Update;
+				Files.Register(file);
+			END;
+			RETURN NIL
+		END ProcessSyntaxTreeModule;
+
+		PROCEDURE DefineOptions(options: Options.Options);
+		BEGIN
+			DefineOptions^(options);
+			options.Add(0X,"dtrace",Options.Flag);
+			options.Add(0X,"docuTemplate", Options.String);
+			options.Add(0X,"internals",Options.Flag);
+		END DefineOptions;
+
+		PROCEDURE GetOptions(options: Options.Options);
+		BEGIN
+			GetOptions^(options);
+			trace := options.GetFlag("dtrace");
+			IF ~options.GetString("d", fileName) THEN fileName := "" END;
+			IF ~options.GetString("docuTemplate", templateFile) THEN COPY(DefaultTemplateFile, templateFile) END;
+			internals := options.GetFlag("internals");
+		END GetOptions;
+
+		PROCEDURE DefaultSymbolFileFormat(): Formats.SymbolFileFormat;
+		BEGIN RETURN SymbolFileFormat.Get()
+		END DefaultSymbolFileFormat;
+
+		PROCEDURE DefaultObjectFileFormat(): Formats.ObjectFileFormat;
+		BEGIN RETURN NIL
+		END DefaultObjectFileFormat;
+
+	END DocumentationBackend;
+
+	(* helper procedures *)
+
+	PROCEDURE Small(CONST name: ARRAY OF CHAR; VAR result: ARRAY OF CHAR);
+	VAR ch: CHAR; i: LONGINT;
+	BEGIN
+		i := 0;
+		REPEAT
+			ch := name[i];
+			IF (ch >= 'A') & (ch <= 'Z') THEN
+				ch := CHR(ORD(ch)-ORD('A')+ORD('a'));
+			END;
+			result[i] := ch; INC(i);
+		UNTIL ch = 0X;
+	END Small;
+
+	PROCEDURE Hex(w: Streams.Writer; x: HUGEINT);
+	VAR i: LONGINT; a: ARRAY 20 OF CHAR; y: HUGEINT;
+	BEGIN
+		i := 0;
+		REPEAT
+			y := x MOD 10H;
+			IF y < 10 THEN a[i] := CHR(y+ORD('0'))
+			ELSE a[i] := CHR(y-10+ORD('A'))
+			END;
+			x := x DIV 10H;
+			INC(i);
+		UNTIL (x=0) OR (i=16);
+		IF y >=10 THEN w.Char("0") END;
+		REPEAT DEC( i ); w.Char( a[i] ) UNTIL i = 0
+	END Hex;
+
+	PROCEDURE ToText(w: Streams.StringWriter; text: DocumentationTree.Text; elementType: ElementType);
+	VAR textElement: DocumentationTree.TextElement; s: DocumentationTree.String;
+	BEGIN
+		NEW(s,w.Pos()+1);
+		w.Get(s^);
+		textElement := text.AppendNew(elementType);
+		textElement.SetString(s);
+		w.SetPos(0); (* reset writer *)
+	END ToText;
+
+	PROCEDURE WriteSymbolLabel(text: DocumentationTree.Text; symbol: SyntaxTree.Symbol);
+	VAR label: Basic.SectionName;
+	BEGIN
+		Global.GetSymbolNameInScope(symbol,NIL,label);
+		text.WriteLabel(label);
+	END WriteSymbolLabel;
+
+
+
+	PROCEDURE WriteReferenceInScope*(text: DocumentationTree.Text; symbol: SyntaxTree.Symbol; inScope: SyntaxTree.Scope);
+	VAR n: SyntaxTree.IdentifierString; td: SyntaxTree.TypeDeclaration; name: Basic.SectionName; write: BOOLEAN;
+		PROCEDURE Scope(scope: SyntaxTree.Scope);
+		BEGIN
+
+			IF scope = NIL THEN (* do nothing, locally declared temporary symbol *)
+			ELSIF scope IS SyntaxTree.ModuleScope THEN
+				Scope(NIL);
+				Global.GetModuleName(scope.ownerModule, name);
+				IF write THEN text.WriteWeakLink(name, name); text.WriteString(".") END;
+				Strings.Append(name,".");
+			ELSIF scope IS SyntaxTree.RecordScope THEN
+				Scope(scope.outerScope);
+				td := scope(SyntaxTree.RecordScope).ownerRecord.typeDeclaration;
+				IF td = NIL THEN
+					td := scope(SyntaxTree.RecordScope).ownerRecord.pointerType.typeDeclaration;
+				END;
+				td.GetName(n);
+				Strings.Append(name,n);
+				IF write THEN text.WriteWeakLink(name, n); text.WriteString(".") END;
+				Strings.Append(name,".")
+			ELSIF scope IS SyntaxTree.ProcedureScope THEN
+				Scope(scope.outerScope);
+				scope(SyntaxTree.ProcedureScope).ownerProcedure.GetName(n);
+				Strings.Append(name,n);
+				IF write THEN text.WriteWeakLink(name, n); text.WriteString(".") END;
+				Strings.Append(name,".")
+			ELSIF scope IS SyntaxTree.CellScope THEN
+				Scope(scope.outerScope);
+				td := scope(SyntaxTree.CellScope).ownerCell.typeDeclaration;
+				td.GetName(n);
+				Strings.Append(name,n);
+				IF write THEN text.WriteWeakLink(name, n); text.WriteString(".") END;
+				Strings.Append(name,".")
+			ELSE Scope(scope.outerScope)
+			END;
+			IF (scope = inScope) THEN write := TRUE END;
+		END Scope;
+
+	BEGIN
+		write := FALSE;
+		name := "";
+		Scope(symbol.scope);
+		symbol.GetName(n);
+		IF symbol IS SyntaxTree.Operator THEN (*! append some more bits to make discrimintation possible *)
+		END;
+		Strings.Append(name,n);
+		text.WriteWeakLink(name, n);
+	END WriteReferenceInScope;
+
+	PROCEDURE WriteSymbolReference(text: DocumentationTree.Text; symbol: SyntaxTree.Symbol; scope: SyntaxTree.Scope);
+	VAR name, label: Basic.SectionName; textElement: DocumentationTree.TextElement;
+	BEGIN
+		WriteReferenceInScope(text,symbol,scope);
+		(*
+		Global.GetSymbolNameInScope(symbol,scope,name);
+		Global.GetSymbolNameInScope(symbol,NIL,label);
+		IF symbol.comment # NIL THEN
+			text.WriteLink(label, name);
+		ELSE
+			text.WriteString(name)
+		END;
+		*)
+	END WriteSymbolReference;
+
+	(*
+	PROCEDURE ConvertSpecialSections(document: DocumentationTree.Document);
+	VAR description: DocumentationTree.Paragraphs; i: LONGINT; section: DocumentationTree.Section; par : DocumentationTree.Paragraph;
+	BEGIN
+		description := document.description;
+		FOR i := document.sections.Length()-1 TO 0 BY -1 DO
+		 section := document.sections.GetSection(i);
+			IF (section.label # NIL) THEN
+				IF section.label^= "author" THEN
+					par := description.AppendNew(ParagraphType.Heading);
+					par.SetLevel(4);
+					par.text.WriteString("Author");
+					par.text.WriteText(section.title);
+					document.sections.RemoveByIndex(i);
+				END;
+			END;
+		END;
+	END ConvertSpecialSections;
+	*)
+
+	PROCEDURE ParseComments(document : DocumentationTree.Document; c,sentinel: SyntaxTree.Comment; x: ANY);
+	VAR string: DocumentationTree.String; stringReader: Streams.StringReader; parser: DocumentationParser.Parser; scanner: DocumentationScanner.Scanner;
+		printer: DocumentationPrinter.Printer;
+	BEGIN
+		IF c # NIL THEN
+			string := MergeComments(c,sentinel,x);
+			NEW(stringReader, LEN(string));
+			stringReader.Set(string^);
+			NEW(scanner, stringReader,0,NIL);
+			NEW(parser, scanner);
+			parser.ParseDocument(document);
+			(*ConvertSpecialSections(document);*)
+		END;
+	END ParseComments;
+
+	(* sentinel used for stopping merging at closing comment of a module if the opening comment comes directly before it.*)
+	PROCEDURE MergeComments(c,sentinel: SyntaxTree.Comment; x: ANY): DocumentationTree.String;
+	VAR i,len: LONGINT; tmp: SyntaxTree.Comment; string: DocumentationTree.String;
+	BEGIN
+		len := 0; tmp := c;
+		WHILE (tmp # sentinel) & (tmp.item = x) DO
+			IF tmp.source[0] = "*" THEN
+				i := 1;
+				WHILE (i<LEN(tmp.source^)) & (tmp.source[i] # 0X) DO
+					INC(i); INC(len);
+				END;
+			END;
+			INC(len,2);
+			tmp := tmp.nextComment;
+		END;
+		NEW(string, len+1);
+		len := 0; tmp := c;
+		WHILE (tmp # sentinel) & (tmp.item = x) DO
+			IF tmp.source[0] = "*" THEN
+				i := 1;
+				WHILE (i<LEN(tmp.source^)) & (tmp.source[i] # 0X) DO
+					string[len] := tmp.source[i];
+					INC(i); INC(len);
+				END;
+				IF (len > 0) & (string[len-1] = "*") THEN DEC(len) END; (* remove last "*" from comment *)
+			END;
+			string[len] := Scanner.CR;
+			INC(len);
+			string[len] := Scanner.CR;
+			INC(len);
+			tmp := tmp.nextComment;
+		END;
+		string[len] := 0X;
+		RETURN string
+	END MergeComments;
+
+	PROCEDURE KeepSections(in: DocumentationTree.Document);
+	VAR i , j: LONGINT; insert, check: DocumentationTree.Section; done: BOOLEAN; paragraph: DocumentationTree.Paragraph; section: DocumentationTree.Section;
+	BEGIN
+			FOR i := 0 TO in.sections.Length()-1 DO
+				section := in.sections.GetSection(i);
+				IF section.title.Length() # 0 THEN
+					paragraph := in.description.AppendNew(ParagraphType.Heading);
+					paragraph.SetLevel(section.level);
+					paragraph.text.WriteText(section.title);
+					MergeParagraphs(in.description, section.contents);
+				END;
+			END;
+	END KeepSections;
+
+	PROCEDURE MergeDocument(in: DocumentationTree.Document; descriptionSection: DocumentationTree.Paragraphs; this: DocumentationTree.Document);
+	VAR i , j: LONGINT; insert, check: DocumentationTree.Section; done: BOOLEAN; paragraph: DocumentationTree.Paragraph; section: DocumentationTree.Section;
+	BEGIN
+		ASSERT(in # NIL);
+		IF descriptionSection = NIL THEN descriptionSection := in.description END;
+		IF this # NIL THEN
+			FOR i := 0 TO this.description.Length()-1 DO
+				descriptionSection.Add(this.description.GetParagraph(i));
+			END;
+			MergeSections(in.sections, this.sections);
+
+			(*
+			FOR i := 0 TO this.sections.Length()-1 DO
+				section := this.sections.GetSection(i);
+				paragraph := this.description.AppendNew(ParagraphType.Heading);
+				paragraph.SetLevel(2);
+				paragraph.text.WriteText(section.title);
+				MergeParagraphs(this.description, section.contents);
+			END;
+			*)
+		END
+	END MergeDocument;
+
+	PROCEDURE MergeParagraphs(in, this: DocumentationTree.Paragraphs);
+	VAR i: LONGINT;
+	BEGIN
+		FOR i := 0 TO this.Length()-1 DO
+			in.AddParagraph(this.GetParagraph(i));
+		END;
+	END MergeParagraphs;
+
+	PROCEDURE MergeSections(in, this: DocumentationTree.Sections);
+	VAR j,i,k,l: LONGINT;  insert, check: DocumentationTree.Section; done: BOOLEAN; par, paragraph,checkPar: DocumentationTree.Paragraph; section: DocumentationTree.Section;
+	BEGIN
+			i := 0;
+			WHILE i < this.Length() DO
+				insert := this.GetSection(i);
+
+				done := FALSE;
+				IF insert.label # NIL THEN
+					j := 0;
+					WHILE (j < in.Length()) (*& ~done*) DO
+						check := in.GetSection(j);
+						IF (check.label # NIL) & (check.label^ = insert.label^) THEN
+							(* first merge all paragraphs *)
+							MergeParagraphs(check.contents, insert.contents);
+							done := TRUE;
+						ELSE
+							k := 0;
+							WHILE k < check.contents.Length() DO
+								checkPar := check.contents.GetParagraph(k);
+								IF (checkPar.type = ParagraphType.Heading) & (checkPar.label # NIL) & (checkPar.label^ = insert.label^) THEN
+									done := TRUE;
+									REPEAT
+										INC(k);
+										IF k < check.contents.Length() THEN
+											par := check.contents.GetParagraph(k);
+										END;
+									UNTIL (k = check.contents.Length()) OR (par.type = ParagraphType.Heading) & (par.level >= checkPar.level);
+									FOR l := 0 TO insert.contents.Length()-1 DO
+										paragraph := insert.contents.GetParagraph(l);
+										check.contents.Insert(k,paragraph);
+										INC(k);
+									END;
+								END;
+								INC(k);
+							END;
+						END;
+						INC(j);
+					END;
+				END;
+				IF ~done THEN
+					in.Add(this.GetSection(i));
+				END;
+				INC(i);
+			END;
+	END MergeSections;
+
+	PROCEDURE MergeSectionDocument(section: DocumentationTree.Section; document: DocumentationTree.Document);
+	VAR paragraphs: DocumentationTree.Paragraphs; i,j: LONGINT; paragraph: DocumentationTree.Paragraph; first: BOOLEAN; firstSection: DocumentationTree.Section;
+	BEGIN
+		NEW(paragraphs, document.description.Length() + section.contents.Length());
+		FOR i := 0 TO document.description.Length()-1 DO
+			paragraphs.Add(document.description.GetParagraph(i));
+		END;
+		FOR i := 0 TO section.contents.Length()-1 DO
+			paragraphs.Add(section.contents.GetParagraph(i));
+		END;
+		section.SetContents(paragraphs);
+
+		FOR i := 0 TO document.sections.Length()-1 DO
+			section := document.sections.GetSection(i);
+			IF (section.label # NIL)  THEN
+				first := TRUE;
+				FOR j := 0 TO i-1 DO
+					firstSection := document.sections.GetSection(j);
+					IF (firstSection.label # NIL) & (firstSection.label^ = section.label^) THEN
+						first := FALSE;
+					END;
+				END;
+
+				IF first THEN
+					paragraph := paragraphs.AppendNew(ParagraphType.Heading);
+					paragraph.SetLevel(2);
+					paragraph.text.WriteString(section.label^);
+					paragraph.text.WriteText(section.title);
+				END;
+				MergeParagraphs(paragraphs, section.contents);
+			END;
+		END;
+
+	END MergeSectionDocument;
+
+
+	PROCEDURE Get*(): Backend.Backend;
+	VAR documentationBackend: DocumentationBackend;
+	BEGIN
+		NEW(documentationBackend); RETURN documentationBackend;
+	END Get;
+
+
+
+END FoxDocumentationBackend.
+
+~~
+
+(**
+= Documentation Backend Description.
+
+This closing comment can be used to describe a whole application.
+It may contain sections like this:
+
+@ Section1
+	* bullet1
+		** bullet2
+		*boldface* (and in same line)
+		*erroneous boldface
+	# bullet3
+		## bullet4
+		## bullet5
+	# bullet 6
+@ Section2
+
+The following is a bulleted list
+
+	\* Bullet1
+	\* Bullet2
+
+
+	This is  a normal text with *
+
+There may be references like this: [[Get|FoxDocumentationBackend.Get]]
+
+
+*)
+
+SystemTools.Free FoxDocumentationHtml FoxDocumentationBackend FoxDocumentationParser FoxDocumentationPrinter FoxDocumentationTree FoxDocumentationScanner ~
+Compiler.Compile -d=*.html -i  oc/FoxDocumentationBackend.Mod ~

+ 244 - 0
source/FoxDocumentationHtml.Mod

@@ -0,0 +1,244 @@
+MODULE FoxDocumentationHtml; (** AUTHOR ""; PURPOSE ""; *)
+
+IMPORT Tree := FoxDocumentationTree, Scanner := FoxDocumentationScanner, Streams,Strings,  SYSTEM;
+
+CONST MaxLevel=256;
+
+TYPE
+	ParagraphType=Tree.ParagraphType;
+	ElementType= Tree.ElementType;
+	Printer*= OBJECT
+	VAR writer: Streams.Writer;
+		chars: ARRAY MaxLevel OF CHAR; listLevel: LONGINT;
+		currentLevel: LONGINT; printLabels: BOOLEAN;
+
+		PROCEDURE &InitPrinter*(writer: Streams.Writer);
+		BEGIN SELF.writer := writer; listLevel := 0; currentLevel := 0; printLabels := FALSE;
+		END InitPrinter;
+
+		PROCEDURE PrintLabel(section: Tree.Section);
+		BEGIN
+			writer.String('"#'); IF section.label = NIL THEN writer.String("_"); writer.Hex(SYSTEM.VAL(LONGINT,section),-8); ELSE WriteString(writer,section.label); END; writer.String('"')
+		END PrintLabel;
+
+		PROCEDURE Section*(section: Tree.Section);
+		BEGIN
+			IF section.label # NIL THEN
+				writer.String('<a name="'); WriteString(writer, section.label); writer.String('"></a>');
+			END;
+			writer.String("<hr>");
+			writer.String("<h"); writer.Int(section.level,0); writer.String(" id="); PrintLabel(section); writer.String(">");
+			Text(section.title); writer.Ln;
+			writer.String("</h"); writer.Int(section.level,1); writer.String(">");
+			ParagraphList(section.contents);
+			(*
+			writer.String('<p><a href="#top"> Top of page </a></p>');
+			*)
+		END Section;
+
+		PROCEDURE SectionList*(sections: Tree.Sections);
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO sections.Length()-1 DO
+				Section(sections.GetSection(i));
+			END;
+		END SectionList;
+
+		PROCEDURE EndItem*(level: LONGINT);
+		BEGIN
+			WHILE listLevel > level DO
+				ASSERT(chars[listLevel] # 0X);
+				writer.String("</"); writer.Char(chars[listLevel]); writer.String("l>");writer.Ln;
+				chars[listLevel] := 0X;
+				DEC(listLevel)
+			END;
+		END EndItem;
+
+		PROCEDURE BeginItem*(level: LONGINT; c: CHAR);
+		BEGIN
+			IF (c # chars[level]) & (level > 0) THEN (* change from bullet to numbers or vice versa *)
+				EndItem(level-1)
+			ELSE
+				EndItem(level);
+			END;
+			WHILE listLevel<level DO
+				writer.String("<"); writer.Char(c); writer.String("l>");writer.Ln;
+				INC(listLevel);
+				chars[listLevel] := c;
+			END;
+		END BeginItem;
+
+		PROCEDURE Paragraph*(paragraph: Tree.Paragraph);
+		VAR element: Tree.TextElement;
+		BEGIN
+			writer.Ln;
+			CASE paragraph.type OF
+			ParagraphType.Heading:
+				EndItem(0);
+				IF paragraph.label # NIL THEN
+					writer.String('<a name="'); WriteString(writer, paragraph.label); writer.String('"></a>');
+				END;
+				writer.String("<h"); writer.Int(currentLevel + paragraph.level,0); writer.String(">"); Text(paragraph.text);
+				writer.String("</h"); writer.Int(currentLevel + paragraph.level,0); writer.String(">");
+			|ParagraphType.TextBlock:
+				EndItem(0);
+				writer.String("<p>"); Text(paragraph.text); writer.String("</p>");
+			|ParagraphType.Number:
+				BeginItem(paragraph.level, 'o');
+				writer.String("<li>");
+				Text(paragraph.text);
+				writer.String("</li>");
+			|ParagraphType.Description:
+				BeginItem(listLevel+1(*paragraph.level*), 'd');
+				writer.String("<dt><strong>"); Text(paragraph.description); writer.String("</strong></dt>");writer.Ln;
+				writer.String("<dd>"); Text(paragraph.text); writer.String("</dd>");writer.Ln;
+				EndItem(listLevel);
+			|ParagraphType.Bullet:
+				BeginItem(paragraph.level, 'u');
+				writer.String("<li>");
+				Text(paragraph.text);
+				writer.String("</li>");
+			|ParagraphType.Code:
+				EndItem(0);
+				writer.String("<pre>"); element := paragraph.text.GetElement(0); Text(paragraph.text); writer.String("</pre>");
+			|ParagraphType.Table:
+				EndItem(0);
+				writer.String('<table>'); Text(paragraph.text); writer.String("</table>");
+			|ParagraphType.Line:
+				EndItem(0);
+				writer.String("<hr/>");
+			ELSE HALT(200)
+			END;
+			writer.Ln;
+		END Paragraph;
+
+		PROCEDURE ParagraphList*(paragraphs: Tree.Paragraphs);
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO paragraphs.Length()-1 DO
+				Paragraph(paragraphs.GetParagraph(i))
+			END;
+			EndItem(0);
+		END ParagraphList;
+
+		PROCEDURE WriteString(writer: Streams.Writer; string: Strings.String);
+		VAR i: LONGINT; c: CHAR;
+		BEGIN
+			IF string = NIL THEN RETURN END;
+			i := 0;
+			WHILE string[i] # 0X DO
+				c := string[i];
+				IF c = '<' THEN writer.String("&lt")
+				ELSIF c = '>' THEN writer.String("&gt")
+				ELSIF c = Scanner.LF THEN writer.String("<BR>")
+				ELSE
+					writer.Char(c);
+				END;
+				INC(i);
+			END;
+		END WriteString;
+
+		PROCEDURE PrintText*(textElement: Tree.TextElement);
+		BEGIN
+			IF textElement.text.Length() = 0 THEN
+				WriteString(writer,textElement.string)
+			ELSE
+				Text(textElement.text);
+			END;
+		END PrintText;
+
+		PROCEDURE TextElement*(textElement: Tree.TextElement);
+		VAR type: ElementType;
+		BEGIN
+			type := textElement.type;
+			(*
+			IF separator # 0X THEN
+				writer.Char(separator)
+			END;
+			*)
+			CASE textElement.type OF
+			ElementType.Default : PrintText(textElement);
+			|ElementType.Whitespace: writer.String(" ");
+			|ElementType.Italic : writer.String("<em>"); PrintText(textElement); writer.String("</em>");
+			|ElementType.Bold : writer.String("<strong>"); PrintText(textElement); writer.String("</strong>");
+			|ElementType.Underline : writer.String("<u>"); PrintText(textElement); writer.String("</u>");
+			|ElementType.HeaderCell : writer.String("<th align='left'>"); PrintText(textElement); writer.String("</th>");
+			|ElementType.DataCell : writer.String("<td>"); PrintText(textElement); writer.String("</td>");
+			|ElementType.Row : writer.String("<tr>"); PrintText(textElement); writer.String("</tr>"); writer.Ln;
+			|ElementType.Link:
+				writer.String('<a href="'); writer.String("#"); WriteString(writer,textElement.string);  writer.String('">');
+				IF textElement.text.Length()#0 THEN
+					 Text(textElement.text);
+				ELSE WriteString( writer, textElement.string);
+				END;
+				writer.String("</a>");
+			|ElementType.Label :
+				writer.String('<a name="'); WriteString(writer, textElement.string); writer.String('"></a>');
+			|ElementType.Code : writer.String("<code>"); PrintText(textElement); writer.String("</code>");
+			|ElementType.LineBreak : writer.String("<br>");
+			END;
+		END TextElement;
+
+
+		PROCEDURE Text*(text: Tree.Text);
+		VAR element: Tree.TextElement; i: LONGINT;
+		BEGIN
+			FOR i := 0 TO text.Length()-1 DO
+				element := text.GetElement(i);
+				TextElement(element);
+			END;
+		END Text;
+
+		PROCEDURE Document*(document: Tree.Document);
+		BEGIN
+			currentLevel := 0;
+			writer.String(
+				\"<?xml version=\"1.0\" encoding=\"UTF-8\" ?> \n"\
+				\"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"> \n"\
+				\"<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title></title> \n"\);
+			writer.String(
+				\"<style>
+				normal {
+				font-family: Helvetica, Sans-Serif;
+				}
+			  .bold {
+				font-family: Helvetica, Sans-Serif;
+				}
+			  H1 {
+				color: #550088
+				}
+			  H2 {
+				font-family: Helvetica, Sans-Serif;
+				color: #550088
+				}
+			  H3 {
+				font-family: Helvetica, Sans-Serif;
+				color: #550088
+				}
+			  H4 {
+				font-family: Helvetica, Sans-Serif;
+				color: #550088
+				}
+			  A {
+				color: #550088
+				}
+				</style>
+				</head>
+				<body>
+			"\);
+
+			currentLevel := 1; printLabels := FALSE;
+			(*
+			writer.String("<h"); writer.Int(currentLevel+1,1); writer.String(">Contents</h"); writer.Int(currentLevel+1,0); writer.String(">");
+			*)
+			ParagraphList(document.description);
+			printLabels := TRUE; EndItem(0);
+			SectionList(document.sections);
+			writer.String("</body></html>"); writer.Ln;
+		END Document;
+
+	END Printer;
+
+END FoxDocumentationHtml.
+
+

+ 315 - 0
source/FoxDocumentationParser.Mod

@@ -0,0 +1,315 @@
+(**
+	@description
+	The documentation parser parses a documentation comment according to the following EBNF (taken from Compiler Suite - Generic DCoocumentation Generation by Florian Negele, slightly modified)
+	and stores the result in a data structure defined in module [[FoxDocumentationParser]].
+
+	{{{
+	Documentation = {Document}.
+	Document      = {NewLine} Description {Section}.
+
+	Description   = {Paragraph}.
+	Paragraph     = CodeBlock | TextBlock | Heading | ListItem  | Table | Line.
+
+	Section       = SectionMarker Title Contents.
+	SectionMarker = "@" | "@@" | "@@@".
+
+	CodeBlock     = "{{{" any text "}}}".
+
+	Contents      = {Paragraph}.
+	Title         = Text.
+	TextBlock     = Text.
+
+	Heading       = HeadingMarker Text.
+	HeadingMarker = "=" | "==" | "===" .
+
+	ListItem      = ItemMarker Text.
+	ItemMarker    = NumberMarker | BulletMarker.
+	NumberMarker  = "#" | "##" | "###".
+	BulletMarker  = "*" | "**" | "***".
+
+	Table         = Row {Row}.
+	Row           = Cell {Cell}.
+	Cell          = CellMarker {TextElement}.
+	CellMarker    = HeaderMarker | DataMarker.
+	HeaderMarker  = "|=".
+	DataMarker    = "|".
+	Line          = "----".
+
+	Text          = {TextElement}.
+	TextElement   = Default | Italic | Bold | Link | URL | Label | Code | LineBreak.
+	Default       = word.
+
+	Italic        = ItalicMarker {TextElement} ItalicMarker.
+	ItalicMarker  = "/".
+
+	Bold          = BoldMarker {TextElement} BoldMarker.
+	BoldMarker    = "*".
+
+	Underline          = UnderlineMarker {TextElement} UnderlineMarker.
+	UnderlineMarker    = "_".
+
+	Link          = "[[" Target [ "|"  {TextElement} ] "]]".
+	Target        = word.
+
+	URL           = "[[" url "]]" |
+	                "[[" url "|" {TextElement} "]]".
+
+	Label         = "<<" Target ">>".
+
+	Code          = "{{{" {TextElement} "}}}".
+
+	LineBreak     = "\\".
+	}}}
+
+	@author Felix Friedrich
+*)
+(** Parser for documentation comments. Independent of the programming language Oberon.
+	Inspired and partially cloned from Florian Negele's Compiler Suite.
+*)
+MODULE FoxDocumentationParser;
+
+IMPORT Scanner := FoxDocumentationScanner, Tree := FoxDocumentationTree, D := Debugging;
+
+CONST Trace = FALSE;
+
+	TYPE
+		Token*= Scanner.Token;
+		ParagraphType*=Tree.ParagraphType;
+		ElementType*=Tree.ElementType;
+
+		Parser*= OBJECT
+		VAR
+			current: Scanner.Symbol;
+			scanner: Scanner.Scanner;
+			inList,italic,bold,link,code, underline: BOOLEAN;
+			previous: Token;
+			whitespace: BOOLEAN;
+
+			PROCEDURE & Init*(scanner: Scanner.Scanner);
+			BEGIN
+				SELF.scanner := scanner;
+				inList := FALSE; italic := FALSE; bold := FALSE; link := FALSE; code := FALSE; underline := FALSE;
+				Next;
+			END Init;
+
+			PROCEDURE Next;
+			BEGIN
+				previous := current.token; whitespace := current.hasWhitespace;scanner.GetNextSymbol(current);
+			END Next;
+
+			PROCEDURE IsCurrent(token: Token): BOOLEAN;
+			BEGIN RETURN current.token = token
+			END IsCurrent;
+
+			PROCEDURE SkipSpace(): LONGINT;
+			VAR num: LONGINT;
+			BEGIN
+				num := 0;
+				WHILE current.token = Token.NewLine DO Next; INC(num) END;
+				RETURN num
+			END SkipSpace;
+
+			PROCEDURE Expect(token: Token);
+			BEGIN
+				IF current.token = token THEN Next
+				ELSE D.String("error in parsing"); D.Ln;
+				END;
+			END Expect;
+
+			PROCEDURE Get(token: Token): BOOLEAN;
+			BEGIN
+				IF current.token = token THEN
+					Next; RETURN TRUE
+				ELSE RETURN FALSE
+				END;
+			END Get;
+
+			PROCEDURE ParseDocument*(document: Tree.Document);
+			VAR num: LONGINT;
+			BEGIN
+				num := SkipSpace();
+				IF current.token # Token.Section THEN
+					ParseParagraphs(document.description)
+				END;
+				ParseSections(document.sections);
+			END ParseDocument;
+
+			PROCEDURE ParseSections(sections: Tree.Sections);
+			VAR section: Tree.Section; num: LONGINT;
+			BEGIN
+				num := SkipSpace();
+				WHILE current.token # Token.EndOfText DO
+					num := SkipSpace();
+					section := sections.AppendNew(current.level);
+					section.SetLabel(current.string);
+					Expect(Token.Section);
+					IF (section.label = NIL) & (current.token = Token.LabelBegin) THEN
+						section.SetLabel(current.string);
+						Next;
+						Expect(Token.LabelEnd);
+					END;
+					ParseText(section.title,Token.NewLine);
+					ParseParagraphs(section.contents); (* will skip end spaces if succesfull *)
+				END;
+			END ParseSections;
+
+			PROCEDURE ParseRow(cells: Tree.Text);
+			VAR element: Tree.TextElement;
+			BEGIN
+				WHILE (current.token = Token.Pipe) OR (current.token = Token.Header) DO
+					IF Trace THEN TRACE("ParseRow", current.position) END;
+					IF current.token = Token.Header THEN element := cells.AppendNew(ElementType.HeaderCell) ELSE element := cells.AppendNew(ElementType.DataCell) END;
+					Next; ParseText(element.text, Token.NewLine);
+				END;
+			END ParseRow;
+
+			PROCEDURE ParseTable(rows: Tree.Text);
+			VAR rowElement: Tree.TextElement;
+			BEGIN
+				WHILE (current.token = Token.Pipe) OR (current.token = Token.Header) DO
+					rowElement := rows.AppendNew(ElementType.Row);
+					ParseRow(rowElement.text); Next;
+				END;
+			END ParseTable;
+
+			PROCEDURE TextStart(sentinel: Token): BOOLEAN;
+			BEGIN
+				RETURN
+				(* not end of text or sentinel *)
+				~IsCurrent( Token.EndOfText) & ~IsCurrent( sentinel)
+				(* not a section start *)
+				& ~IsCurrent(Token.Section)
+				(* not a paragraph end *)
+				& (~IsCurrent(Token.NewLine) OR (previous # Token.NewLine))
+				(* not a heading *)
+				& ~IsCurrent(Token.Heading)
+				(* not a list *)
+				& ~IsCurrent(Token.Number) & ~IsCurrent(Token.Bullet) &~IsCurrent(Token.LeftDescription)
+				(* not a table *)
+				& ~IsCurrent(Token.Pipe) & ~IsCurrent(Token.Header)
+			END TextStart;
+
+			(* insert whitespace, if present *)
+
+			PROCEDURE ParseText(text: Tree.Text; sentinel: Token);
+			VAR element: Tree.TextElement; pos: LONGINT;
+				PROCEDURE Whitespace;
+				BEGIN
+					IF whitespace THEN text.WriteWhitespace() END
+				END Whitespace;
+			BEGIN
+				(* sentinel for stopping parsing when, for example, italic or boldface ends *)
+				WHILE TextStart(sentinel) DO
+					IF ~italic & Get(Token.LeftItalic) THEN
+						italic := TRUE;
+						Whitespace;
+						element := text.AppendNew(ElementType.Italic);
+						ParseText(element.text, Token.RightItalic);
+						italic := FALSE;
+					ELSIF ~bold & Get(Token.LeftBold) THEN
+						bold := TRUE;
+						Whitespace;
+						element := text.AppendNew(ElementType.Bold);
+						ParseText(element.text, Token.RightBold);
+						bold := FALSE
+					ELSIF ~underline & Get(Token.LeftUnderline) THEN
+						underline := TRUE;
+						Whitespace;
+						element := text.AppendNew(ElementType.Underline);
+						ParseText(element.text, Token.RightUnderline);
+						underline := FALSE
+					ELSIF ~link & Get(Token.LinkBegin) THEN
+						Whitespace;
+						element := text.AppendNew(ElementType.Link);
+						element.SetString(current.string);
+						Expect(Token.String);
+						IF Get(Token.Pipe) THEN link := TRUE; ParseText(element.text,Token.LinkEnd); link := FALSE; END;
+						Expect(Token.LinkEnd);
+					ELSIF Get(Token.LabelBegin) THEN
+						Whitespace;
+						element := text.AppendNew(ElementType.Label);
+						element.SetString(current.string);
+						Expect(Token.String);
+						Expect(Token.LabelEnd);
+					ELSIF Get(Token.CodeBegin) THEN
+						Whitespace;
+						code := TRUE;
+						element := text.AppendNew(ElementType.Code);
+						element.SetString(current.string);
+						Expect(Token.CodeEnd);
+						(*ParseText(element.text, Token.CodeEnd);*)
+						code := FALSE
+					ELSIF Get(Token.LineBreak) THEN
+						element := text.AppendNew(ElementType.LineBreak);
+					ELSIF Get(Token.NewLine) THEN
+						(*element := text.AppendNew(ElementType.LineBreak);*)
+					ELSE
+						IF current.hasWhitespace THEN text.WriteWhitespace END;
+						element := text.AppendNew(ElementType.Default);
+						element. SetString(current.string);
+						Next;
+					END;
+				END;
+			END ParseText;
+
+			PROCEDURE ParseParagraphs(paragraphs: Tree.Paragraphs);
+			VAR paragraph: Tree.Paragraph; element: Tree.TextElement; nl: LONGINT;
+			BEGIN
+				nl := SkipSpace();
+				WHILE (current.token # Token.EndOfText) & (current.token # Token.Section)  DO
+					CASE current.token OF
+					Token.Heading:
+						inList := FALSE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Heading);
+						paragraph.SetLevel(current.level);
+						paragraph.SetLabel(current.string);
+						Next;
+						IF (paragraph.label = NIL) & (current.token = Token.LabelBegin) THEN
+							paragraph.SetLabel(current.string);
+							Next;
+							Expect(Token.LabelEnd);
+						END;
+						ParseText(paragraph.text, Token.NewLine);
+					|Token.Number :
+						inList := TRUE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Number); paragraph.SetLevel(current.level);
+						Next; ParseText(paragraph.text, Token.EndOfText);
+					|Token.LeftDescription:
+						inList := TRUE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Description); paragraph.SetLevel(current.level);
+						Next; ParseText(paragraph.description, Token.RightDescription); ParseText(paragraph.text, Token.LeftDescription);
+					|Token.Description:
+						inList := TRUE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Description); paragraph.SetLevel(current.level);
+						paragraph.description.WriteString(current.string^); Next; ParseText(paragraph.text, Token.Description);
+					|Token.Bullet:
+						inList := TRUE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Bullet); paragraph.SetLevel(current.level);
+						Next; ParseText(paragraph.text, Token.EndOfText);
+					|Token.CodeBegin :
+						inList := FALSE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Code);
+						Next;
+						element := paragraph.text.AppendNew(ElementType.Default); element.SetString(current.string); Expect(Token.CodeEnd);
+					|Token.Pipe, Token.Header :
+						paragraph := paragraphs.AppendNew(ParagraphType.Table);
+						ParseTable(paragraph.text);
+						Next;
+					|Token.Line :
+						inList := FALSE;
+						paragraph := paragraphs.AppendNew(ParagraphType.Line);
+						Next;
+					ELSE
+						inList := FALSE;
+						paragraph := paragraphs.AppendNew(ParagraphType.TextBlock); ParseText(paragraph.text, Token.EndOfText);
+					END;
+					inList := FALSE;
+					nl := SkipSpace();
+					IF nl > 1 THEN inList := FALSE END;
+				END;
+			END ParseParagraphs;
+
+		END Parser;
+
+
+END FoxDocumentationParser.

+ 155 - 0
source/FoxDocumentationPrinter.Mod

@@ -0,0 +1,155 @@
+MODULE FoxDocumentationPrinter; (** AUTHOR ""; PURPOSE ""; *)
+
+IMPORT Tree := FoxDocumentationTree, Scanner := FoxDocumentationScanner, Streams;
+
+CONST debug=FALSE;
+
+TYPE
+	ParagraphType=Tree.ParagraphType;
+	ElementType= Tree.ElementType;
+	Printer*= OBJECT
+	VAR writer: Streams.Writer;
+
+		PROCEDURE &InitPrinter*(writer: Streams.Writer);
+		BEGIN SELF.writer := writer
+		END InitPrinter;
+
+		PROCEDURE Section*(section: Tree.Section);
+		BEGIN
+			IF debug THEN writer.Ln; writer.String("//// Section with level "); writer.Int(section.level,1); writer.String("//// "); END;
+			writer.Ln;
+			Chars("@", section.level);
+			IF section.label # NIL THEN writer.String(section.label^) END;
+			Text(section.title); writer.Ln;
+			ParagraphList(section.contents);
+		END Section;
+
+		PROCEDURE SectionList*(sections: Tree.Sections);
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO sections.Length()-1 DO
+				Section(sections.GetSection(i));
+			END;
+		END SectionList;
+
+		PROCEDURE Document*(document: Tree.Document);
+		BEGIN
+			ParagraphList(document.description);
+			SectionList(document.sections);
+		END Document;
+
+		PROCEDURE Chars*(c: CHAR; rep: LONGINT);
+		BEGIN
+			WHILE rep>0 DO writer.Char(c); DEC(rep) END;
+		END Chars;
+
+		PROCEDURE Paragraph*(paragraph: Tree.Paragraph);
+		BEGIN
+			IF debug THEN
+				writer.Ln;
+				writer.String("//// paragraph of type ");
+				CASE paragraph.type OF
+				ParagraphType.Heading: writer.String("Heading");
+				|ParagraphType.TextBlock: writer.String("TextBlock");
+				|ParagraphType.Number: writer.String("Number");
+				|ParagraphType.Bullet: writer.String("Bullet")
+				|ParagraphType.Code: writer.String("Code")
+				|ParagraphType.Table: writer.String("Table");
+				|ParagraphType.Line: writer.String("Line");
+				|ParagraphType.Description: writer.String("Description")
+				END;
+				writer.String("//// ");
+			END;
+			writer.Ln;
+			CASE paragraph.type OF
+			ParagraphType.Heading: Chars('=', paragraph.level);
+				IF paragraph.label # NIL THEN writer.String(paragraph.label^); END;
+				Text(paragraph.text);
+			|ParagraphType.Description: writer.String("#"); Text(paragraph.description); writer.String("#"); Text(paragraph.text);
+			|ParagraphType.TextBlock: Text(paragraph.text);
+			|ParagraphType.Number: Chars('#', paragraph.level); Text(paragraph.text);
+			|ParagraphType.Bullet: Chars('*', paragraph.level); Text(paragraph.text);
+			|ParagraphType.Code: writer.String("{{{"); writer.Ln; Text(paragraph.text); writer.Ln; writer.String("}}}");
+			|ParagraphType.Table: Text(paragraph.text);
+			|ParagraphType.Line: writer.String("----");
+			ELSE HALT(200)
+			END;
+			writer.Ln;
+		END Paragraph;
+
+		PROCEDURE ParagraphList*(paragraphs: Tree.Paragraphs);
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO paragraphs.Length()-1 DO
+				Paragraph(paragraphs.GetParagraph(i))
+			END;
+		END ParagraphList;
+
+		PROCEDURE PrintText*(textElement: Tree.TextElement; separator: CHAR);
+		BEGIN
+			IF textElement.text.Length() = 0 THEN
+				IF textElement.string # NIL THEN
+					writer.String(textElement.string^);
+				END;
+			ELSE
+				Text(textElement.text);
+			END;
+		END PrintText;
+
+		PROCEDURE TextElement*(textElement: Tree.TextElement);
+		BEGIN
+			IF debug THEN
+				writer.Ln; writer.String("//// TextElement:");
+				CASE textElement.type OF
+				ElementType.Default : writer.String("Default")
+				|ElementType.Italic :  writer.String("Italic")
+				|ElementType.Bold :  writer.String("Bold")
+				|ElementType.HeaderCell :  writer.String("HeaderCell")
+				|ElementType.DataCell :  writer.String("DataCell")
+				|ElementType.Row :  writer.String("Row")
+				|ElementType.Link :  writer.String("Link")
+				|ElementType.WeakLink :  writer.String("Link")
+				|ElementType.Label :  writer.String("Label")
+				|ElementType.Code :  writer.String("Code")
+				|ElementType.LineBreak :  writer.String("LineBreak")
+				ELSE END;
+				writer.String("//// ");
+			END;
+			(*
+			IF separator # 0X THEN
+				writer.Char(separator)
+			END;
+			*)
+			CASE textElement.type OF
+			ElementType.Default : PrintText(textElement,0X);
+			|ElementType.Whitespace: writer.String(" ");
+			|ElementType.Italic : writer.String("/"); PrintText(textElement,0X); writer.String("/");
+			|ElementType.Underline : writer.String("_"); PrintText(textElement,0X); writer.String("_");
+			|ElementType.Bold : writer.String("*"); PrintText(textElement,0X); writer.String("*");
+			|ElementType.HeaderCell : writer.String("|="); PrintText(textElement,' ');
+			|ElementType.DataCell : writer.String("|"); PrintText(textElement,' ');
+			|ElementType.Row : PrintText(textElement,0X); writer.Ln;
+			|ElementType.Link, ElementType.WeakLink :
+				writer.String("[["); writer.String(textElement.string^);
+				IF textElement.text.Length()#0 THEN
+					writer.String("|");  Text(textElement.text);
+				END;
+				writer.String("]]");
+			|ElementType.Label : writer.String("<<"); writer.String(textElement.string^); writer.String(">>");
+			|ElementType.Code : writer.String("{{{"); PrintText(textElement,0X); writer.String("}}}");
+			|ElementType.LineBreak : writer.String("\\");
+			END;
+		END TextElement;
+
+		PROCEDURE Text*(text: Tree.Text);
+		VAR element: Tree.TextElement; i: LONGINT;
+		BEGIN
+			FOR i := 0 TO text.Length()-1 DO
+				element := text.GetElement(i);
+				TextElement(element);
+			END;
+		END Text;
+
+	END Printer;
+
+END FoxDocumentationPrinter.

+ 351 - 0
source/FoxDocumentationScanner.Mod

@@ -0,0 +1,351 @@
+MODULE FoxDocumentationScanner; (** AUTHOR ""; PURPOSE ""; *)
+
+IMPORT Streams, Diagnostics, D := Debugging;
+
+
+CONST
+	(* scanner constants *)
+	EOT* = 0X; LF* = 0AX; CR* = 0DX;
+	Trace=FALSE;
+TYPE
+
+	Token*= ENUM
+		EndOfText*, NewLine*, Header*, Pipe*, LeftItalic*, RightItalic*, LeftBold*, RightBold*, LeftUnderline*, RightUnderline*,
+		LinkBegin*, LinkEnd*, Heading*, Number*, LeftDescription*, RightDescription*, Description*,
+		Bullet*, Line*, CodeBegin*, CodeEnd*, LabelBegin*, LabelEnd*,
+		LineBreak*, Section*, String*
+	END;
+
+	String*= POINTER TO ARRAY OF CHAR;
+
+	Symbol*= RECORD
+		position*: LONGINT;
+		token*: Token;
+		level*: LONGINT;
+		string*: String;
+		stringLength*: LONGINT;
+		hasWhitespace*: BOOLEAN;
+	END;
+
+	(** scanner reflects the following EBNF
+	**)
+	Scanner* = OBJECT
+	VAR
+		(* helper state information *)
+		reader: Streams.Reader;   (* source *)
+		diagnostics: Diagnostics.Diagnostics;   (* error logging *)
+
+		ch: CHAR;   (* look-ahead character *)
+		position-: LONGINT;   (* current position *)
+		prevToken: Token;
+
+		(*
+			source: name of the source code for reference in error outputs
+			reader: input stream
+			position: reference position (offset) of the input stream , for error output
+			diagnostics: error output object
+		*)
+		PROCEDURE & InitializeScanner*(reader: Streams.Reader; position: LONGINT; diagnostics: Diagnostics.Diagnostics);
+		BEGIN
+			SELF.reader := reader;
+			SELF.diagnostics := diagnostics;
+			ch := " ";
+			IF reader = NIL THEN ch := EOT ELSE GetNextCharacter END;
+			SELF.position := position;
+		END InitializeScanner;
+
+		(** get next character, end of text results in ch = EOT **)
+		PROCEDURE GetNextCharacter;
+		BEGIN
+			ASSERT(ch # EOT);
+			reader.Char(ch); INC(position);
+		END GetNextCharacter;
+
+		PROCEDURE Peek(): CHAR;
+		BEGIN
+			RETURN reader.Peek()
+		END Peek;
+
+		PROCEDURE BreaksLiteral(): BOOLEAN;
+		BEGIN
+			CASE ch OF
+				"*", "_", "/","#":
+					IF IsWhitespace(Peek(),TRUE) THEN RETURN TRUE END; (* right of bold, underline, italics and description *)
+				|"]",">","\":
+					IF ch = Peek() THEN RETURN TRUE END; (* right of link or label *)
+				|"|": RETURN TRUE (* pipe in link or table *)
+			ELSE
+				RETURN FALSE
+			END;
+			RETURN FALSE
+		END BreaksLiteral;
+
+		PROCEDURE IdentifierStart(ch: CHAR): BOOLEAN;
+		BEGIN
+			CASE ch OF
+				| 'a' .. 'z', 'A' .. 'Z', '_': RETURN TRUE
+			ELSE RETURN FALSE
+			END;
+		END IdentifierStart;
+
+		PROCEDURE AppendCharacter(VAR symbol: Symbol; ch: CHAR);
+		VAR s: String;  i: LONGINT;
+		BEGIN
+			IF symbol.string = NIL THEN NEW(symbol.string,32); symbol.stringLength := 0; END;
+			IF symbol.stringLength = LEN(symbol.string)-1 THEN
+				s := symbol.string;
+				NEW(symbol.string, symbol.stringLength*2);
+				FOR i := 0 TO symbol.stringLength DO
+					symbol.string[i] := s[i];
+				END;
+			END;
+			symbol.string[symbol.stringLength] := ch;
+			INC(symbol.stringLength);
+		END AppendCharacter;
+
+		PROCEDURE AppendCharacters(VAR symbol: Symbol; ch: CHAR; number: LONGINT);
+		BEGIN
+			WHILE number > 0 DO
+				AppendCharacter(symbol,ch); DEC(number)
+			END;
+		END AppendCharacters;
+
+		PROCEDURE ReadLiteral(VAR symbol: Symbol; token: Token);
+		BEGIN
+			symbol.token := token;
+			REPEAT (* consumes at least one character *)
+				AppendCharacter(symbol, ch);
+				GetNextCharacter;
+			UNTIL (ch <= " ") OR BreaksLiteral();
+		END ReadLiteral;
+
+		PROCEDURE ReadLiteralWS(VAR symbol: Symbol; token: Token);
+		BEGIN
+			symbol.token := token;
+			REPEAT (* consumes at least one character *)
+				AppendCharacter(symbol, ch);
+				GetNextCharacter;
+			UNTIL (ch <= " ");
+		END ReadLiteralWS;
+
+		PROCEDURE ReadCharacters(this: CHAR; min,max: LONGINT; VAR symbol: Symbol; token: Token);
+		BEGIN
+			symbol.level := 0;
+			WHILE (ch = this) DO INC(symbol.level); GetNextCharacter END;
+			IF (symbol.level >= min) OR (symbol.level <= max) THEN
+				symbol.token := token;
+			ELSE
+				AppendCharacters(symbol, this, symbol.level);
+				ReadLiteral(symbol, Token.String);
+			END;
+		END ReadCharacters;
+
+		PROCEDURE IsWhitespace(ch: CHAR; includeNewLine: BOOLEAN): BOOLEAN;
+		BEGIN
+			RETURN (ch <= " ") & (ch # EOT) & (includeNewLine OR (ch # CR) & (ch # LF))
+		END IsWhitespace;
+
+		(** get next symbol **)
+		PROCEDURE GetNextSymbol*(VAR symbol: Symbol);
+		VAR s: Token; prev: CHAR; firstInLine: BOOLEAN;
+
+			PROCEDURE SkipBlanks;
+			BEGIN
+				WHILE IsWhitespace(ch,FALSE) DO  (*ignore control characters*)
+					prev := ch;
+					GetNextCharacter
+				END;
+			END SkipBlanks;
+
+			PROCEDURE ReadCode;
+			VAR ending: LONGINT; end:BOOLEAN;
+			BEGIN
+				ending := 0; end := FALSE;
+				REPEAT
+					AppendCharacter(symbol, ch);
+					IF ch = "}" THEN
+						INC(ending);
+						GetNextCharacter
+					ELSIF (ending = 3) & (ch # "'") & (ch # '"')  (* to allow for referencing '}}}' in descriptions of the documentation *)& (ending = 3) THEN
+						end := TRUE
+					ELSE
+						ending := 0;
+						GetNextCharacter
+					END;
+				UNTIL (ch = EOT) OR end;
+				IF end THEN
+					symbol.token := Token.CodeEnd;
+					DEC(symbol.stringLength,4);
+					symbol.string[symbol.stringLength] := 0X
+				ELSE
+					symbol.token := Token.EndOfText
+				END;
+			END ReadCode;
+
+			(* return if the current character is preceded by a whitespace and next character is not a whitespace, such as in the beginning of *b o l d*  *)
+			PROCEDURE IsLeft(): BOOLEAN;
+			BEGIN
+				RETURN IsWhitespace(prev,FALSE) & ~IsWhitespace(Peek(),FALSE)
+			END IsLeft;
+
+			(* return if the current character is preceded by a non-whitespace and next character is a whitespace, such as at the end of *b o l d*  *)
+			PROCEDURE IsRight(): BOOLEAN;
+			BEGIN
+				RETURN ~IsWhitespace(prev,TRUE) & IsWhitespace(Peek(),TRUE)
+			END IsRight;
+
+
+		BEGIN
+			symbol.stringLength := 0;
+			symbol.string := NIL;
+			symbol.level := 0;
+			symbol.position := position;
+
+			prev := ch;
+			SkipBlanks;
+			firstInLine := prevToken = Token.NewLine;
+
+			symbol.hasWhitespace := IsWhitespace(prev, FALSE);
+
+			IF symbol.token = Token.CodeBegin THEN
+				ReadCode;
+				RETURN
+			END;
+
+
+			CASE ch OF  (* ch > " " *)
+			EOT: symbol.token := Token.EndOfText
+			| CR: symbol.token := Token.NewLine; GetNextCharacter; IF ch = LF THEN GetNextCharacter END;
+			| LF: symbol.token := Token.NewLine; GetNextCharacter; IF ch = CR THEN GetNextCharacter END;
+			|  '|': GetNextCharacter;
+				IF (ch = "=") THEN symbol.token := Token.Header; GetNextCharacter ELSE symbol.token := Token.Pipe END;
+			|  '/':
+				IF IsLeft() THEN  symbol.token := Token.LeftItalic; GetNextCharacter;
+				ELSIF IsRight() THEN symbol.token := Token.RightItalic; GetNextCharacter;
+				ELSE ReadLiteral( symbol, Token.String)
+				END
+			|  '_':
+				IF IsLeft() THEN  symbol.token := Token.LeftUnderline;GetNextCharacter;
+				ELSIF IsRight() THEN symbol.token := Token.RightUnderline;GetNextCharacter;
+				ELSE ReadLiteral( symbol, Token.String)
+				END
+			|  '[': ReadCharacters (ch, 2, 2, symbol, Token.LinkBegin);
+			|  ']': ReadCharacters (ch, 2, 2, symbol, Token.LinkEnd);
+			|  '=':
+				IF firstInLine THEN
+					ReadCharacters (ch, 1, 3, symbol, Token.Heading);
+					IF IdentifierStart(ch) THEN
+						WHILE ~IsWhitespace(ch,TRUE) & (ch#EOT) DO
+							AppendCharacter(symbol,ch);
+							GetNextCharacter;
+						END;
+					END;
+				ELSE ReadLiteral(symbol, Token.String);
+				END;
+			|  '#':
+				IF firstInLine THEN (* number *)
+					ReadCharacters(ch, 1, 3, symbol, Token.Number);
+					IF IsWhitespace(ch,FALSE) THEN
+						symbol.token := Token.Number;
+					ELSIF symbol.level = 1 THEN
+						symbol.token := Token.LeftDescription;
+					ELSE
+						ReadLiteral(symbol, Token.String);
+					END;
+				ELSE
+					IF IsLeft() THEN symbol.token := Token.LeftDescription; symbol.level := 1; GetNextCharacter;
+					ELSIF IsRight() THEN symbol.token := Token.RightDescription;GetNextCharacter;
+					ELSE ReadLiteral(symbol, Token.String);
+					END;
+				END;
+			|  '*':
+				IF firstInLine THEN
+					ReadCharacters(ch, 1, 3, symbol, Token.Bullet);
+					IF IsWhitespace(ch,FALSE) THEN
+						symbol.token := Token.Bullet;
+					ELSIF symbol.level = 1 THEN
+						symbol.token := Token.LeftBold;
+					ELSE
+						AppendCharacters(symbol, '*', symbol.level);
+						ReadLiteral(symbol, Token.String);
+					END;
+				ELSE
+					IF IsLeft() THEN symbol.token := Token.LeftBold;GetNextCharacter;
+					ELSIF  IsRight() THEN symbol.token := Token.RightBold;GetNextCharacter;
+					ELSE ReadLiteral(symbol, Token.String);
+					END;
+				END;
+			|  '-':
+				IF firstInLine THEN ReadCharacters (ch, 4, MAX(LONGINT), symbol, Token.Line)
+				ELSE ReadLiteral(symbol, Token.String);
+				END;
+			|  '{': ReadCharacters (ch, 3, 3, symbol, Token.CodeBegin);
+			|  '}': ReadCharacters (ch, 3, 3, symbol, Token.CodeEnd);
+			|  '<':
+				ReadCharacters (ch, 2, 2, symbol, Token.LabelBegin);
+				IF IsWhitespace(ch, TRUE) & (symbol.level = 2) THEN
+					AppendCharacters(symbol,'<',2);
+					symbol.token := Token.String
+				END;
+			|  '>':
+				ReadCharacters (ch, 2, 2, symbol, Token.LabelEnd);
+				IF IsWhitespace(prev, FALSE) & (symbol.level = 2) THEN
+					AppendCharacters(symbol,'>',2);
+					ReadLiteral(symbol, Token.String);
+				END;
+			|  '\':
+				ReadCharacters (ch, 2, 2, symbol, Token.LineBreak);
+			|  '@': ReadCharacters (ch, 1, 10, symbol, Token.Section);
+				IF IdentifierStart(ch) THEN
+					WHILE ~IsWhitespace(ch,TRUE) & (ch#EOT) DO
+						AppendCharacter(symbol,ch);
+						GetNextCharacter;
+					END;
+				END;
+			ELSE
+				ReadLiteral(symbol,Token.String)
+			END;
+			prevToken := symbol.token;
+
+			IF (firstInLine) & (symbol.token = Token.String) & (symbol.stringLength>0) & (symbol.string[symbol.stringLength-1] = ":") THEN
+				DEC(symbol.stringLength);
+				symbol.string[symbol.stringLength] := 0X;
+				symbol.token := Token.Description;
+			END;
+
+			IF Trace THEN DumpSymbol(D.Log, symbol); D.Ln END;
+		END GetNextSymbol;
+
+	END Scanner;
+
+	PROCEDURE DumpSymbol(w: Streams.Writer; CONST symbol: Symbol);
+	BEGIN
+		w.String("token: ");
+		CASE symbol.token OF
+			Token.EndOfText: w.String("EndOfText");
+			|Token.NewLine: w.String("NewLine");
+			|Token.Header:w.String("Header");
+			|Token.Pipe:w.String("Pipe");
+			|Token.LeftItalic:w.String("LeftItalic");
+			|Token.RightItalic:w.String("RightItalic");
+			|Token.LinkBegin:w.String("LinkBegin");
+			|Token.LinkEnd:w.String("LinkEnd");
+			|Token.Heading:w.String("Heading");
+			|Token.Number:w.String("Number");
+			|Token.Bullet:w.String("Bullet");
+			|Token.LeftBold:w.String("LeftBold");
+			|Token.RightBold:w.String("RightBold");
+			|Token.LeftUnderline:w.String("LeftUnderline");
+			|Token.RightUnderline:w.String("RightUnderline");
+			|Token.Line:w.String("Line");
+			|Token.CodeBegin:w.String("CodeBegin");
+			|Token.CodeEnd:w.String("CodeEnd");
+			|Token.LabelBegin:w.String("LabelBegin");
+			|Token.LabelEnd:w.String("LabelEnd");
+			|Token.LineBreak:w.String("LineBreak");
+			|Token.Section:w.String("Section");
+			|Token.String:w.String("String");
+		END;
+	END DumpSymbol;
+
+END FoxDocumentationScanner.

+ 397 - 0
source/FoxDocumentationTree.Mod

@@ -0,0 +1,397 @@
+(**
+	Documentation tree to represent a module documentation. Independent of source language. \
+	Implemented according to Compiler Suite - Generic Documentation Generation by Florian Negele.
+
+	The documentation tree represents the [[docuEBNF|EBNF]] as implemented by [[FoxDocumentationParser]]
+	#author# Felix Friedrich
+*)
+MODULE FoxDocumentationTree;
+(**
+	@concept
+	= FoxDocumentationTree
+
+	All information that has been parsed by the documentation parser is stored in a documentation tree. The documentation tree is implemented in module \
+	[[FoxDocumentationTree]].
+	The root of such a documentation is a [[FoxDocumenationTree.Document|Document]], it conists of a description and a number of sections. Each [[Section|section]] consist of a title, cotents and \
+	can have a label. Sections that actually have a label are merged by the DocumentationBackend. A rough picture is also provided by the [[docuEBNF|EBNF]].
+
+	==docuEBNF EBNF of the documentation in a paragraph
+
+	@@docuEBNF <<docuEBNF>> EBNF of the documentation
+		The following EBNF of the documentation cannot be taken literally because some additional rules are implemented in scanner and parser. However it provides a picture \
+		of the documentation rules and how they are parsed.
+**)
+
+
+IMPORT Basic := FoxBasic, Scanner := FoxDocumentationScanner, Strings;
+
+TYPE
+	String*= Scanner.String;
+
+	ElementType* = ENUM
+		Whitespace*, Default*, Italic*, Bold*, Underline*, Link*, WeakLink*, Label*, Code*, LineBreak*, DataCell*, HeaderCell*, Row*
+	END;
+
+	(**
+		@docuEBNF
+		{{{TextElement = String |BeginItalic Text EndItalic | BeginBold Text EndBold | "[[" string | Text "]]" | "<<" String ">>" | "{{{" any text "}}}" | "\\" | "|" Text | "|=" Text | }}}
+	**)
+	TextElement*= OBJECT
+	VAR
+		type-: ElementType;
+		string-: String;
+		text-: Text;
+
+		PROCEDURE &InitTextElement*(type: ElementType);
+		BEGIN
+			NEW(text,4);
+			SELF.type := type;
+			string := NIL;
+		END InitTextElement;
+
+		PROCEDURE SetString*(string: String);
+		BEGIN SELF.string := string
+		END SetString;
+
+		PROCEDURE SetType*(t: ElementType);
+		BEGIN
+			type := t
+		END SetType;
+
+	END TextElement;
+
+
+	(**
+		Comment of Text
+		@docuEBNF EBNF
+		{{{ ElementList = {Element} }}}
+	*)
+	Text*=OBJECT (Basic.List)
+
+		PROCEDURE AppendNew*(type: ElementType): TextElement;
+		VAR textElement: TextElement;
+		BEGIN
+			NEW(textElement,type); AddElement(textElement); RETURN textElement
+		END AppendNew;
+
+		PROCEDURE AddElement*(x: TextElement);
+		BEGIN  Add(x)
+		END AddElement;
+
+		PROCEDURE GetElement*(index: LONGINT): TextElement;
+		VAR x: ANY;
+		BEGIN x := Get(index); IF x = NIL THEN RETURN NIL ELSE RETURN x(TextElement) END;
+		END GetElement;
+
+		PROCEDURE FindByString*(CONST string: ARRAY OF CHAR): TextElement;
+		VAR i: LONGINT; element: TextElement;
+		BEGIN
+			FOR i := 0 TO Length()-1 DO
+				element := GetElement(i);
+				IF (element.string#NIL) & (element.string^ = string)  THEN RETURN element END;
+			END;
+			RETURN NIL
+		END FindByString;
+
+		PROCEDURE WriteString*(CONST s: ARRAY OF CHAR);
+		VAR e: TextElement;
+		BEGIN
+			e := AppendNew(ElementType.Default);
+			e.string := Strings.NewString(s);
+		END WriteString;
+
+		PROCEDURE WriteWhitespace*();
+		VAR e: TextElement;
+		BEGIN
+			e := AppendNew(ElementType.Whitespace);
+		END WriteWhitespace;
+
+
+		PROCEDURE WriteLink*(CONST name, label: ARRAY OF CHAR);
+		VAR e: TextElement;
+		BEGIN
+			e := AppendNew(ElementType.Link);
+			e.string := Strings.NewString(name);
+			e.text.WriteString(label);
+		END WriteLink;
+
+		PROCEDURE WriteWeakLink*(CONST name, label: ARRAY OF CHAR);
+		VAR e: TextElement;
+		BEGIN
+			e := AppendNew(ElementType.WeakLink);
+			e.string := Strings.NewString(name);
+			e.text.WriteString(label);
+		END WriteWeakLink;
+
+		PROCEDURE WriteText*(t: Text);
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO t.Length()-1 DO
+				AddElement(t.GetElement(i));
+			END;
+		END WriteText;
+
+		PROCEDURE WriteLabel*(CONST name: ARRAY OF CHAR);
+		VAR e: TextElement;
+		BEGIN
+			e := AppendNew(ElementType.Label);
+			e.string := Strings.NewString(name);
+		END WriteLabel;
+
+		PROCEDURE Last*(): TextElement;
+		BEGIN RETURN GetElement(Length()-1)
+		END Last;
+
+		PROCEDURE ForAllElementsDo*(this: PROCEDURE {DELEGATE} (x: TextElement));
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO Length()-1 DO
+				this(GetElement(i))
+			END;
+		END ForAllElementsDo;
+
+	END Text;
+
+	Word*=ARRAY 32 OF CHAR;
+
+	ParagraphType* = ENUM
+		TextBlock*, Heading*, Bullet*, Number*, Description*, Code*, Line*, Table*
+	END;
+
+	(**
+		@@docuEBNF EBNF
+		{{{ paragraph = Text |  Heading Text | InsertLabel | Bullet Text | Number Text | Description Text | CodeBegin Text CodeEnd | Line | Table Text. }}}
+	*)
+	Paragraph*=OBJECT
+	VAR
+		type-: ParagraphType;
+		level-: LONGINT;
+		text*: Text;
+		description-: Text;
+		label*: Strings.String;
+
+		PROCEDURE SetLabel*(label: String);
+		BEGIN SELF.label := label
+		END SetLabel;
+
+		PROCEDURE & InitParagraph*(type: ParagraphType);
+		BEGIN SELF.type := type; SELF.level := level; NEW(text,4); NEW(description, 4);
+		END InitParagraph;
+
+		PROCEDURE SetLevel*(level: LONGINT);
+		BEGIN SELF.level := level
+		END SetLevel;
+
+
+	END Paragraph;
+
+
+	(**
+		@docuEBNF
+		 {{{ Paragraphs = {Paragraph}. }}}
+	*)
+	Paragraphs*=OBJECT (Basic.List)
+
+		PROCEDURE AppendNew*(type: ParagraphType): Paragraph;
+		VAR paragraph: Paragraph;
+		BEGIN
+			NEW(paragraph,type); AddParagraph(paragraph); RETURN paragraph
+		END AppendNew;
+
+		PROCEDURE AddParagraph*(x: Paragraph);
+		BEGIN  Add(x)
+		END AddParagraph;
+
+		PROCEDURE GetParagraph*(index: LONGINT): Paragraph;
+		VAR x: ANY;
+		BEGIN x := Get(index); IF x = NIL THEN RETURN NIL ELSE RETURN x(Paragraph) END;
+		END GetParagraph;
+
+		PROCEDURE Last*(): Paragraph;
+		BEGIN RETURN GetParagraph(Length()-1)
+		END Last;
+
+		PROCEDURE ForAllParagraphsDo*(this: PROCEDURE {DELEGATE} (x: Paragraph));
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO Length()-1 DO
+				this(GetParagraph(i))
+			END;
+		END ForAllParagraphsDo;
+
+	END Paragraphs;
+
+	(**
+		@docuEBNF
+		{{{ Section = "@"{"@"} Title Contents. }}}
+		{{{ Contents = {Paragraph}. }}}
+	*)
+	Section*=OBJECT
+	VAR
+		level-: LONGINT;
+		title-: Text;
+		contents-: Paragraphs;
+		label-: String;
+
+		PROCEDURE &InitSection*(level: LONGINT);
+		BEGIN NEW(contents,4); SELF.level := level; NEW(title,4); label := NIL;
+		END InitSection;
+
+		PROCEDURE SetLabel*(label: String);
+		BEGIN SELF.label := label
+		END SetLabel;
+
+		PROCEDURE SetContents*(paragraphs: Paragraphs);
+		BEGIN contents := paragraphs
+		END SetContents;
+
+		PROCEDURE WriteLabel*(CONST l: ARRAY OF CHAR);
+		BEGIN
+			label := Strings.NewString(l);
+		END WriteLabel;
+
+		PROCEDURE WriteHeading*(level: LONGINT; CONST string: ARRAY OF CHAR);
+		VAR paragraph: Paragraph;
+		BEGIN
+			paragraph := contents.AppendNew(ParagraphType.Heading);
+			paragraph.SetLevel(level);
+			paragraph.text.WriteString(string);
+		END WriteHeading;
+
+	END Section;
+
+	(**
+		@docuEBNF
+		{{{ SectionList = {Section} }}}
+	*)
+	Sections*=OBJECT (Basic.List)
+
+		PROCEDURE AppendNew*(level: LONGINT): Section;
+		VAR section: Section;
+		BEGIN
+			NEW(section, level); AddSection(section); RETURN section;
+		END AppendNew;
+
+		PROCEDURE AddSection*(x: Section);
+		BEGIN  Add(x)
+		END AddSection;
+
+		PROCEDURE GetSection*(index: LONGINT): Section;
+		VAR x: ANY;
+		BEGIN x := Get(index); IF x = NIL THEN RETURN NIL ELSE RETURN x(Section) END;
+		END GetSection;
+
+		PROCEDURE Last*(): Section;
+		BEGIN RETURN GetSection(Length()-1)
+		END Last;
+
+		PROCEDURE ForAllSectionsDo*(this: PROCEDURE {DELEGATE} (x: Section));
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO Length()-1 DO
+				this(GetSection(i))
+			END;
+		END ForAllSectionsDo;
+
+	END Sections;
+
+
+	(** a document provides the documentation of a module in a structured way *)
+	(**
+		@docuEBNF
+		{{{ Document = Description {Section}. }}}
+		{{{ Description = {Paragraph}. }}}
+	*)
+	Document*=OBJECT
+	VAR
+		description-: Paragraphs;
+		sections-: Sections;
+
+		PROCEDURE &InitDocument*;
+		BEGIN NEW(sections,4); NEW(description,4);
+		END InitDocument;
+
+	END Document;
+
+	(**
+		@docuEBNF
+		{{{Documents = {Document}. }}}
+	*)
+	Documents*=OBJECT (Basic.List)
+		PROCEDURE AddDocument*(x: Document);
+		BEGIN  Add(x)
+		END AddDocument;
+
+		PROCEDURE GetDocument*(index: LONGINT): Document;
+		VAR x: ANY;
+		BEGIN x := Get(index); IF x = NIL THEN RETURN NIL ELSE RETURN x(Document) END;
+		END GetDocument;
+
+		PROCEDURE ForAllDocumentsDo*(this: PROCEDURE {DELEGATE} (x: Document));
+		VAR i: LONGINT;
+		BEGIN
+			FOR i := 0 TO Length()-1 DO
+				this(GetDocument(i))
+			END;
+		END ForAllDocumentsDo;
+
+	END Documents;
+
+	(** a documentation provides the documentation of several modules *)
+	(**
+		@docuEBNF
+		{{{ Documentation =  {Document}. }}}
+	*)
+	Documentation*=OBJECT
+	VAR
+		documents-: Documents
+
+		PROCEDURE &InitDocumentation*;
+		BEGIN NEW(documents,4)
+		END InitDocumentation;
+
+
+	END Documentation;
+
+	PROCEDURE NewText*(): Text;
+	VAR text: Text;
+	BEGIN NEW(text,4); RETURN text
+	END NewText;
+
+	PROCEDURE NewTextElement*(type: ElementType): TextElement;
+	VAR textElement: TextElement;
+	BEGIN NEW(textElement,type); RETURN textElement
+	END NewTextElement;
+
+	PROCEDURE NewParagraph*(type: ParagraphType): Paragraph;
+	VAR paragraph: Paragraph;
+	BEGIN NEW(paragraph,type); RETURN paragraph
+	END NewParagraph;
+
+	PROCEDURE NewParagraphs*(): Paragraphs;
+	VAR paragraphs: Paragraphs;
+	BEGIN NEW(paragraphs,4); RETURN paragraphs
+	END NewParagraphs;
+
+	PROCEDURE NewSection*(level: LONGINT): Section;
+	VAR section: Section;
+	BEGIN NEW(section,level); RETURN section
+	END NewSection;
+
+	PROCEDURE NewSections*(level: LONGINT): Sections;
+	VAR sections: Sections;
+	BEGIN NEW(sections,4); RETURN sections
+	END NewSections;
+
+	PROCEDURE NewDocument*(): Document;
+	VAR document: Document;
+	BEGIN NEW(document); RETURN document
+	END NewDocument;
+
+	PROCEDURE Test;
+	VAR e: TextElement;
+	BEGIN
+		e := NewTextElement(ElementType.Default);
+	END Test;
+
+END FoxDocumentationTree.