FoxDocumentationParser.Mod 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. (**
  2. @description
  3. The documentation parser parses a documentation comment according to the following EBNF and stores the result in a data structure defined in module [[FoxDocumentationParser]].
  4. {{{
  5. Documentation = {Document}.
  6. Document = {NewLine} Description {Section}.
  7. Description = {Paragraph}.
  8. Paragraph = CodeBlock | TextBlock | Heading | ListItem | Table | Line.
  9. Section = SectionMarker Title Contents.
  10. SectionMarker = "@" | "@@" | "@@@".
  11. CodeBlock = "{{{" any text "}}}".
  12. Contents = {Paragraph}.
  13. Title = Text.
  14. TextBlock = Text.
  15. Heading = HeadingMarker Text.
  16. HeadingMarker = "=" | "==" | "===" .
  17. ListItem = ItemMarker Text.
  18. ItemMarker = NumberMarker | BulletMarker.
  19. NumberMarker = "#" | "##" | "###".
  20. BulletMarker = "*" | "**" | "***".
  21. Table = Row {Row}.
  22. Row = Cell {Cell}.
  23. Cell = CellMarker {TextElement}.
  24. CellMarker = HeaderMarker | DataMarker.
  25. HeaderMarker = "|=".
  26. DataMarker = "|".
  27. Line = "----".
  28. Text = {TextElement}.
  29. TextElement = Default | Italic | Bold | Link | URL | Label | Code | LineBreak.
  30. Default = word.
  31. Italic = ItalicMarker {TextElement} ItalicMarker.
  32. ItalicMarker = "/".
  33. Bold = BoldMarker {TextElement} BoldMarker.
  34. BoldMarker = "*".
  35. Underline = UnderlineMarker {TextElement} UnderlineMarker.
  36. UnderlineMarker = "_".
  37. Link = "[[" Target [ "|" {TextElement} ] "]]".
  38. Target = word.
  39. URL = "[[" url "]]" |
  40. "[[" url "|" {TextElement} "]]".
  41. Label = "<<" Target ">>".
  42. Code = "{{{" {TextElement} "}}}".
  43. LineBreak = "\\".
  44. }}}
  45. @author Felix Friedrich
  46. *)
  47. (** Parser for documentation comments. Independent of the programming language Oberon. *)
  48. MODULE FoxDocumentationParser;
  49. IMPORT Scanner := FoxDocumentationScanner, Tree := FoxDocumentationTree, D := Debugging;
  50. CONST Trace = FALSE;
  51. TYPE
  52. Token*= Scanner.Token;
  53. ParagraphType*=Tree.ParagraphType;
  54. ElementType*=Tree.ElementType;
  55. Parser*= OBJECT
  56. VAR
  57. current: Scanner.Symbol;
  58. scanner: Scanner.Scanner;
  59. inList,italic,bold,link,code, underline: BOOLEAN;
  60. previous: Token;
  61. whitespace: BOOLEAN;
  62. PROCEDURE & Init*(scanner: Scanner.Scanner);
  63. BEGIN
  64. SELF.scanner := scanner;
  65. inList := FALSE; italic := FALSE; bold := FALSE; link := FALSE; code := FALSE; underline := FALSE;
  66. Next;
  67. END Init;
  68. PROCEDURE Next;
  69. BEGIN
  70. previous := current.token; whitespace := current.hasWhitespace;scanner.GetNextSymbol(current);
  71. END Next;
  72. PROCEDURE IsCurrent(token: Token): BOOLEAN;
  73. BEGIN RETURN current.token = token
  74. END IsCurrent;
  75. PROCEDURE SkipSpace(): LONGINT;
  76. VAR num: LONGINT;
  77. BEGIN
  78. num := 0;
  79. WHILE current.token = Token.NewLine DO Next; INC(num) END;
  80. RETURN num
  81. END SkipSpace;
  82. PROCEDURE Expect(token: Token);
  83. BEGIN
  84. IF current.token = token THEN Next
  85. ELSE D.String("error in parsing"); D.Ln;
  86. END;
  87. END Expect;
  88. PROCEDURE Get(token: Token): BOOLEAN;
  89. BEGIN
  90. IF current.token = token THEN
  91. Next; RETURN TRUE
  92. ELSE RETURN FALSE
  93. END;
  94. END Get;
  95. PROCEDURE ParseDocument*(document: Tree.Document);
  96. VAR num: LONGINT;
  97. BEGIN
  98. num := SkipSpace();
  99. IF current.token # Token.Section THEN
  100. ParseParagraphs(document.description)
  101. END;
  102. ParseSections(document.sections);
  103. END ParseDocument;
  104. PROCEDURE ParseSections(sections: Tree.Sections);
  105. VAR section: Tree.Section; num: LONGINT;
  106. BEGIN
  107. num := SkipSpace();
  108. WHILE current.token # Token.EndOfText DO
  109. num := SkipSpace();
  110. section := sections.AppendNew(current.level);
  111. section.SetLabel(current.string);
  112. Expect(Token.Section);
  113. IF (section.label = NIL) & (current.token = Token.LabelBegin) THEN
  114. section.SetLabel(current.string);
  115. Next;
  116. Expect(Token.LabelEnd);
  117. END;
  118. ParseText(section.title,Token.NewLine);
  119. ParseParagraphs(section.contents); (* will skip end spaces if succesfull *)
  120. END;
  121. END ParseSections;
  122. PROCEDURE ParseRow(cells: Tree.Text);
  123. VAR element: Tree.TextElement;
  124. BEGIN
  125. WHILE (current.token = Token.Pipe) OR (current.token = Token.Header) DO
  126. IF Trace THEN TRACE("ParseRow", current.position) END;
  127. IF current.token = Token.Header THEN element := cells.AppendNew(ElementType.HeaderCell) ELSE element := cells.AppendNew(ElementType.DataCell) END;
  128. Next; ParseText(element.text, Token.NewLine);
  129. END;
  130. END ParseRow;
  131. PROCEDURE ParseTable(rows: Tree.Text);
  132. VAR rowElement: Tree.TextElement;
  133. BEGIN
  134. WHILE (current.token = Token.Pipe) OR (current.token = Token.Header) DO
  135. rowElement := rows.AppendNew(ElementType.Row);
  136. ParseRow(rowElement.text); Next;
  137. END;
  138. END ParseTable;
  139. PROCEDURE TextStart(sentinel: Token): BOOLEAN;
  140. BEGIN
  141. RETURN
  142. (* not end of text or sentinel *)
  143. ~IsCurrent( Token.EndOfText) & ~IsCurrent( sentinel)
  144. (* not a section start *)
  145. & ~IsCurrent(Token.Section)
  146. (* not a paragraph end *)
  147. & (~IsCurrent(Token.NewLine) OR (previous # Token.NewLine))
  148. (* not a heading *)
  149. & ~IsCurrent(Token.Heading)
  150. (* not a list *)
  151. & ~IsCurrent(Token.Number) & ~IsCurrent(Token.Bullet) &~IsCurrent(Token.LeftDescription)
  152. (* not a table *)
  153. & ~IsCurrent(Token.Pipe) & ~IsCurrent(Token.Header)
  154. END TextStart;
  155. (* insert whitespace, if present *)
  156. PROCEDURE ParseText(text: Tree.Text; sentinel: Token);
  157. VAR element: Tree.TextElement; pos: LONGINT;
  158. PROCEDURE Whitespace;
  159. BEGIN
  160. IF whitespace THEN text.WriteWhitespace() END
  161. END Whitespace;
  162. BEGIN
  163. (* sentinel for stopping parsing when, for example, italic or boldface ends *)
  164. WHILE TextStart(sentinel) DO
  165. IF ~italic & Get(Token.LeftItalic) THEN
  166. italic := TRUE;
  167. Whitespace;
  168. element := text.AppendNew(ElementType.Italic);
  169. ParseText(element.text, Token.RightItalic);
  170. italic := FALSE;
  171. ELSIF ~bold & Get(Token.LeftBold) THEN
  172. bold := TRUE;
  173. Whitespace;
  174. element := text.AppendNew(ElementType.Bold);
  175. ParseText(element.text, Token.RightBold);
  176. bold := FALSE
  177. ELSIF ~underline & Get(Token.LeftUnderline) THEN
  178. underline := TRUE;
  179. Whitespace;
  180. element := text.AppendNew(ElementType.Underline);
  181. ParseText(element.text, Token.RightUnderline);
  182. underline := FALSE
  183. ELSIF ~link & Get(Token.LinkBegin) THEN
  184. Whitespace;
  185. element := text.AppendNew(ElementType.Link);
  186. element.SetString(current.string);
  187. Expect(Token.String);
  188. IF Get(Token.Pipe) THEN link := TRUE; ParseText(element.text,Token.LinkEnd); link := FALSE; END;
  189. Expect(Token.LinkEnd);
  190. ELSIF Get(Token.LabelBegin) THEN
  191. Whitespace;
  192. element := text.AppendNew(ElementType.Label);
  193. element.SetString(current.string);
  194. Expect(Token.String);
  195. Expect(Token.LabelEnd);
  196. ELSIF Get(Token.CodeBegin) THEN
  197. Whitespace;
  198. code := TRUE;
  199. element := text.AppendNew(ElementType.Code);
  200. element.SetString(current.string);
  201. Expect(Token.CodeEnd);
  202. (*ParseText(element.text, Token.CodeEnd);*)
  203. code := FALSE
  204. ELSIF Get(Token.LineBreak) THEN
  205. element := text.AppendNew(ElementType.LineBreak);
  206. ELSIF Get(Token.NewLine) THEN
  207. (*element := text.AppendNew(ElementType.LineBreak);*)
  208. ELSE
  209. IF current.hasWhitespace THEN text.WriteWhitespace END;
  210. element := text.AppendNew(ElementType.Default);
  211. element. SetString(current.string);
  212. Next;
  213. END;
  214. END;
  215. END ParseText;
  216. PROCEDURE ParseParagraphs(paragraphs: Tree.Paragraphs);
  217. VAR paragraph: Tree.Paragraph; element: Tree.TextElement; nl: LONGINT;
  218. BEGIN
  219. nl := SkipSpace();
  220. WHILE (current.token # Token.EndOfText) & (current.token # Token.Section) DO
  221. CASE current.token OF
  222. Token.Heading:
  223. inList := FALSE;
  224. paragraph := paragraphs.AppendNew(ParagraphType.Heading);
  225. paragraph.SetLevel(current.level);
  226. paragraph.SetLabel(current.string);
  227. Next;
  228. IF (paragraph.label = NIL) & (current.token = Token.LabelBegin) THEN
  229. paragraph.SetLabel(current.string);
  230. Next;
  231. Expect(Token.LabelEnd);
  232. END;
  233. ParseText(paragraph.text, Token.NewLine);
  234. |Token.Number :
  235. inList := TRUE;
  236. paragraph := paragraphs.AppendNew(ParagraphType.Number); paragraph.SetLevel(current.level);
  237. Next; ParseText(paragraph.text, Token.EndOfText);
  238. |Token.LeftDescription:
  239. inList := TRUE;
  240. paragraph := paragraphs.AppendNew(ParagraphType.Description); paragraph.SetLevel(current.level);
  241. Next; ParseText(paragraph.description, Token.RightDescription); ParseText(paragraph.text, Token.LeftDescription);
  242. |Token.Description:
  243. inList := TRUE;
  244. paragraph := paragraphs.AppendNew(ParagraphType.Description); paragraph.SetLevel(current.level);
  245. paragraph.description.WriteString(current.string^); Next; ParseText(paragraph.text, Token.Description);
  246. |Token.Bullet:
  247. inList := TRUE;
  248. paragraph := paragraphs.AppendNew(ParagraphType.Bullet); paragraph.SetLevel(current.level);
  249. Next; ParseText(paragraph.text, Token.EndOfText);
  250. |Token.CodeBegin :
  251. inList := FALSE;
  252. paragraph := paragraphs.AppendNew(ParagraphType.Code);
  253. Next;
  254. element := paragraph.text.AppendNew(ElementType.Default); element.SetString(current.string); Expect(Token.CodeEnd);
  255. |Token.Pipe, Token.Header :
  256. paragraph := paragraphs.AppendNew(ParagraphType.Table);
  257. ParseTable(paragraph.text);
  258. Next;
  259. |Token.Line :
  260. inList := FALSE;
  261. paragraph := paragraphs.AppendNew(ParagraphType.Line);
  262. Next;
  263. ELSE
  264. inList := FALSE;
  265. paragraph := paragraphs.AppendNew(ParagraphType.TextBlock); ParseText(paragraph.text, Token.EndOfText);
  266. END;
  267. inList := FALSE;
  268. nl := SkipSpace();
  269. IF nl > 1 THEN inList := FALSE END;
  270. END;
  271. END ParseParagraphs;
  272. END Parser;
  273. END FoxDocumentationParser.