Oberon to Markdown

This is the twelfth post in the Mostly Oberon series. Mostly Oberon documents my exploration of the Oberon Language, Oberon System and the various rabbit holes I will inevitably fall into.

A nice feature of Oberon

Oberon source code has a very nice property in that anything after the closing end statement is ignored by the compiler. This makes it a nice place to write documentation, program notes and other ideas.

I’ve gotten in the habit of writing up program docs and notes there. When I prep to make a web document I used to copy the source file, doing a cut and paste to re-order the module code to the bottom of the document. I’d follow that with adding headers and code fences. Not hard but tedious. Of course if I changed the source code I’d also have to do another cut and paste edit. This program, ObnToMd.Mod automates that process.

Program Documentation

  1. PROGRAM
  2. ObnToMd
  3. FUNCTION
  4. This is a simple program that reads Oberon modules
  5. from standard in and re-renders that to standard output
  6. such that it is suitable to process with Pandoc or other
  7. text processing system.
  8. EXAMPLE
  9. Read the source for this program and render a file
  10. called "blog-post.md". Use Pandoc to render HTML.
  11. ObnToMd <ObnToMd.Mod > blog-post.md
  12. pandoc -s --metadata title="Blog Post" \
  13. blog-post.md >blog-post.html
  14. BUGS
  15. It uses a naive line analysis to identify the module
  16. name and then the end of module statement. Might be
  17. tripped up by comments containing the same strings.
  18. The temporary file created is called "o2m.tmp" and
  19. this filename could potentially conflict with another
  20. file.

Source code for ObnToMd.Mod

  1. (* ObnToMd.Mod - an simple filter process for reading
  2. an Oberon-07 module source file and rendering a markdown
  3. friendly output suitable for piping into Pandoc. The
  4. filter reads from standard input and writes to standard
  5. output and makes use of a temp file name o2m.tmp which
  6. it removes after successful rendering.
  7. @Author R. S. Doiel, <rsdoiel@gmail.com>
  8. copyright (c) 2020, all rights reserved.
  9. Released under the BSD 2-clause license
  10. See: https://opensource.org/licenses/BSD-2-Clause
  11. *)
  12. MODULE ObnToMd;
  13. IMPORT In, Out, Files, Strings;
  14. CONST
  15. MAXLENGTH = 1024;
  16. LF = CHR(10);
  17. VAR
  18. endOfLine : ARRAY 2 OF CHAR;
  19. (*
  20. * Helper methods
  21. *)
  22. PROCEDURE GenTempName(prefix, suffix : ARRAY OF CHAR; VAR name : ARRAY OF CHAR);
  23. BEGIN
  24. name := "";
  25. Strings.Append(prefix, name);
  26. Strings.Append(".", name);
  27. Strings.Append(suffix, name);
  28. END GenTempName;
  29. PROCEDURE GenTempFile(name : ARRAY OF CHAR; VAR r : Files.Rider; VAR f : Files.File);
  30. BEGIN
  31. f := Files.New(name);
  32. IF f = NIL THEN
  33. Out.String("ERROR: can't create ");Out.String(name);Out.Ln();
  34. ASSERT(FALSE);
  35. END;
  36. Files.Register(f);
  37. Files.Set(r, f, 0);
  38. END GenTempFile;
  39. PROCEDURE StartsWith(target, source : ARRAY OF CHAR) : BOOLEAN;
  40. VAR res : BOOLEAN;
  41. BEGIN
  42. IF Strings.Pos(target, source, 0) > -1 THEN
  43. res := TRUE;
  44. ELSE
  45. res := FALSE;
  46. END;
  47. RETURN res
  48. END StartsWith;
  49. PROCEDURE ClearString(VAR s : ARRAY OF CHAR);
  50. VAR i : INTEGER;
  51. BEGIN
  52. FOR i := 0 TO LEN(s) - 1 DO
  53. s[i] := 0X;
  54. END;
  55. END ClearString;
  56. PROCEDURE ProcessModuleDef(VAR r : Files.Rider; VAR modName : ARRAY OF CHAR);
  57. VAR
  58. line, endStmt : ARRAY MAXLENGTH OF CHAR;
  59. start, end : INTEGER;
  60. BEGIN
  61. line := "";
  62. endStmt := "";
  63. modName := "";
  64. (* Find the name of the module and calc the "END {NAME}." statement *)
  65. REPEAT
  66. ClearString(line);
  67. In.Line(line);
  68. IF In.Done THEN
  69. Files.WriteString(r, line); Files.WriteString(r, endOfLine);
  70. (* When `MODULE {NAME};` is encountered extract the module name *)
  71. IF StartsWith("MODULE ", line) THEN
  72. start := 7;
  73. end := Strings.Pos(";", line, 0);
  74. IF (end > -1) & (end > start) THEN
  75. Strings.Extract(line, start, end - start, modName);
  76. endStmt := "END ";
  77. Strings.Append(modName, endStmt);
  78. Strings.Append(".", endStmt);
  79. END;
  80. END;
  81. END;
  82. UNTIL (In.Done # TRUE) OR (endStmt # "");
  83. (* When `END {NAME}.` is encountered stop writing tmp file *)
  84. REPEAT
  85. In.Line(line);
  86. IF In.Done THEN
  87. Files.WriteString(r, line); Files.WriteString(r, endOfLine);
  88. END;
  89. UNTIL (In.Done # TRUE) OR StartsWith(endStmt, line);
  90. END ProcessModuleDef;
  91. PROCEDURE WriteModuleDef(name : ARRAY OF CHAR; VAR r : Files.Rider; VAR f : Files.File);
  92. VAR s : ARRAY MAXLENGTH OF CHAR; res : INTEGER;
  93. BEGIN
  94. Files.Set(r, f, 0);
  95. REPEAT
  96. Files.ReadString(r, s);
  97. IF r.eof # TRUE THEN
  98. Out.String(s);
  99. END;
  100. UNTIL r.eof;
  101. Files.Close(f);
  102. Files.Delete(name, res);
  103. END WriteModuleDef;
  104. PROCEDURE OberonToMarkdown();
  105. VAR
  106. tmpName, modName, line : ARRAY MAXLENGTH OF CHAR;
  107. f : Files.File;
  108. r : Files.Rider;
  109. i : INTEGER;
  110. BEGIN
  111. tmpName := ""; modName := ""; line := "";
  112. (* Open temp file *)
  113. GenTempName("o2m", "tmp", tmpName);
  114. GenTempFile(tmpName, r, f);
  115. (* Read the Oberon source from standard input echo the lines tmp file *)
  116. ProcessModuleDef(r, modName);
  117. (* Write remainder of file to standard out *)
  118. REPEAT
  119. In.Line(line);
  120. IF In.Done THEN
  121. Out.String(line);Out.Ln();
  122. END;
  123. UNTIL In.Done # TRUE;
  124. (* Write two new lines *)
  125. Out.Ln(); Out.Ln();
  126. (* Write heading `Source code for {NAME}` *)
  127. ClearString(line);
  128. line := "Source code for **";
  129. Strings.Append(modName, line);
  130. Strings.Append(".Mod**", line);
  131. Out.String(line); Out.Ln();
  132. FOR i := 0 TO Strings.Length(line) - 1 DO
  133. Out.String("-");
  134. END;
  135. Out.Ln();
  136. (* Write code fence *)
  137. Out.Ln();Out.String("~~~");Out.Ln();
  138. (* Reset rider to top of tmp file
  139. Write temp file to standard out
  140. cleanup demp file *)
  141. WriteModuleDef(tmpName, r, f);
  142. (* Write code fence *)
  143. Out.Ln();Out.String("~~~");Out.Ln();
  144. (* Write tailing line and exit procedure *)
  145. Out.Ln();
  146. END OberonToMarkdown;
  147. BEGIN
  148. endOfLine[0] := LF;
  149. endOfLine[1] := 0X;
  150. OberonToMarkdown();
  151. END ObnToMd.

Next, Previous