#! /usr/bin/env retro # RETRO Source to HTML RETRO programs are written using a literate format called Unu which allows mixing of code and commentary in a somewhat literate format. Code (and optionally tests) is extracted from fenced blocks, and commentary normally uses a subset of Markdown. This tool processes both parts, generating formatted HTML documents that look nice, and also provides syntax highlighting for the test and code blocks. ## Features For Markdown: - lists - indented code blocks - paragraphs - headers - fenced code and test blocks - horizontal rules - *inline* _formatting_ elements For RETRO: - syntax highlighting of most elements - uses introspection to identify primitives For both: - easily customizable via CSS at the end of this file ## Limitations This only supports a limited subset of full Markdown. I am not adding support for the various linking formats, ordered lists, underlined headers, doubled asterisk, doubled underscores, multiple line/paragraph list entries, or images. The formatting must follow the following limits: # Title ## Subtitle ### Level 3 Title Paragraph. *Bold*, _italics_, `inline code`. Another paragraph. - list element - list element - nested list element - nested list element - list element ~~~ :retro (code ; ~~~ ``` tests ``` Sample code or output with four leading spaces is note colorized. this will be code, but not run through the colorizer. More paragraph text. ---- Above is a horizontal separator. This is not very intelligent. If you have text like `3 * 4`, it'll happily treat the \* as the start of a bold sequence. Use a leading backslash to avoid this. ---- ## The Code ### Headers and CSS Injection HTML is pretty verbose and wants a bunch of boilerplate to work nicely, so I start with some header stuff. ~~~ ' s:put nl ' s:put nl ' s:put nl '. s:put nl ~~~ Locate and embed the CSS from the end of this file. The CSS will be at the end of the file, starting with the line reading "\#\# CSS". ~~~ ' s:put nl FALSE script:name [ over [ '##_CSS s:eq? or ] -if; s:put nl ] file:for-each-line drop ' s:put nl ~~~ Finish the header boilerplate and switch to the body. ~~~ ' s:put nl '

s:put ~~~ ### Support Code The first couple of words are a variation of `s:put` that generates HTML codes for specific characters. This ensures that code output displays correctly. ~~~ :c:put $< [ '< s:put ] case $> [ '> s:put ] case $& [ '& s:put ] case ASCII:SPACE [ '  s:put ] case c:put ; :s:put [ c:put ] s:for-each ; ~~~ For regular text, there are a couple of inline formatting things to deal with. These are: * emphasis * strong (bold) * escaped characters * code ~~~ 'Emphasis var 'Strong var 'Escape var 'Code var ~~~ ~~~ :format $` [ @Escape [ &Escape v:off $* c:put ] if; @Code n:zero? [ ' &Code v:on ] [ ' &Code v:off ] choose s:put ] case $* [ @Escape @Code or [ &Escape v:off $* c:put ] if; @Strong n:zero? [ ' &Strong v:on ] [ ' &Strong v:off ] choose s:put ] case $_ [ @Escape @Code or [ &Escape v:off $_ c:put ] if; @Emphasis n:zero? [ ' &Emphasis v:on ] [ ' &Emphasis v:off ] choose s:put ] case $\ [ &Escape v:on ] case ASCII:SPACE [ sp ] case c:put ; :s:put [ format ] s:for-each ; ~~~ ### Markdown Elements *Code and Test Blocks* The biggest element is the code and test blocks. These will be generated in an enclosure that looks like:

... code ...
The actual words in the code will be in `` elements. The fences need to start and end with `~~~` or three backticks on a line by itself. So, identifying and generating an HTML container for a code block is a matter of: ~~~ {{ 'Block var :begin '~~~
; :end '~~~ ; ---reveal--- :in-code-block? (-f) @Block ; :code-block? (s-sf) dup '~~~ s:eq? ; :toggle-code (n-) drop @Block n:zero? dup &begin &end choose s:put !Block ; }} ~~~ And test blocks are basically the same, except for the delimiters. ~~~ {{ 'Block var :begin '```
; :end '``` ; ---reveal--- :in-test-block? (-f) @Block ; :test-block? (s-sf) dup '``` s:eq? ; :toggle-test (n-) drop @Block n:zero? dup &begin &end choose s:put !Block ; }} ~~~ On to generating the actual HTML for the syntax highlighted source. This is driven by the prefix, then by word class via a little quick introspection. ~~~ {{ :span (s-) ' s:put s:put '_ s:put ; ---reveal--- :format-code (s-) (ignore_empty_tokens) dup s:length n:zero? [ '  s:put drop ] if; (tokens_with_prefixes) dup fetch $: [ 'colon span ] case $( [ 'note span ] case $' [ 'str span ] case $# [ 'num span ] case $. [ 'fnum span ] case $& [ 'ptr span ] case $$ [ 'char span ] case $` [ 'inst span ] case $\ [ 'inst span ] case $| [ 'defer span ] case $@ [ 'fetch span ] case $! [ 'store span ] case (immediate_and_primitives) drop dup d:lookup d:class fetch &class:macro [ 'imm span ] case &class:primitive [ 'prim span ] case drop (normal_words) s:put sp ; :colorize ASCII:SPACE s:tokenize &format-code a:for-each ; :format:code ' s:put colorize '
s:put nl ; }} ~~~ *Headers* After this, I define detection and formatting of headers. The headers should look like: # Level 1 ## Level 2 ### Level 3 ~~~ :header? dup [ '#_ s:begins-with? ] [ '##_ s:begins-with? ] [ '###_ s:begins-with? ] tri or or over '####_ s:begins-with? or ; :format:head ASCII:SPACE s:split '# [ ' s:put n:inc s:put ' s:put nl ] s:case '## [ ' s:put n:inc s:put ' s:put nl ] s:case '### [ ' s:put n:inc s:put ' s:put nl ] s:case '#### [ ' s:put n:inc s:put ' s:put nl ] s:case drop ; ~~~ *Indented Code Blocks* Indented code blocks are lines indented by four spaces. These are *not* syntax highlighted as they are ignored by Unu. ~~~ :inline-code? dup '____ s:begins-with? ; :format:inline-code ' s:put #4 + s:put ' s:put nl ; ~~~ *Horizontal Rules* Horizonal rules consist of four or more - characters on a line. E.g., ---- -------- This also accepts sequences of `-+-+` which were used in some older RETRO source files. ~~~ :rule? dup [ '---- s:begins-with? ] [ '-+-+ s:begins-with? ] bi or ; :format:rule drop ' s:put nl ; ~~~ *Lists* Lists start with a `-` or `*`, followed by a space, then the item text. Additionally, this allows for nested lists starting with two spaces before the list marker. ~~~ :list? dup [ '-_ s:begins-with? ] [ '*_ s:begins-with? ] bi or ; :format:list '•_ s:put #2 + s:put '
s:put nl ; :indented-list? dup [ '__-_ s:begins-with? ] [ '__*_ s:begins-with? ] bi or ; :format:indented-list '• s:put #3 + s:put '
s:put nl ; ~~~ *Paragraphs* Blank lines denote paragraph breaks. ~~~ :blank? dup s:length n:zero? ; 'InParagraph var ~~~ *The Formatter* This ties together the various words above, generating the output. ~~~ :format s:keep code-block? [ toggle-code ] if; in-code-block? [ format:code ] if; test-block? [ toggle-test ] if; in-test-block? [ format:code ] if; blank? [ drop '

s:put nl ] if; header? [ format:head ] if; inline-code? [ format:inline-code ] if; list? [ format:list ] if; indented-list? [ format:indented-list ] if; rule? [ format:rule ] if; s:put nl ; #0 script:get-argument [ &Heap &format v:preserve ] file:for-each-line reset '

s:put nl ' s:put nl ~~~ This concludes the Markdown (subset) in RETRO utility. All that's left is the CSS. ## CSS * { color: #000; background: #fff; max-width: 700px; } tt, pre { background: #dedede; color: #111; font-family: monospace; white-space: pre; display: block; width: 100%; } .indentedcode { margin-left: 2em; margin-right: 2em; } .codeblock { background: #dedede; color: #111; font-family: monospace; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); padding: 7px; display: block; } .indentedlist { margin-left: 2em; color: #000; } span { white-space: pre; } .text { color: #000; white-space: pre; background: #dedede; } .colon { color: #000; background: #dedede; } .note { color: #000; background: #dedede; } .str { color: #000; text-decoration: underline; background: #dedede; } .num { color: #000; background: #dedede; font-weight: bold; font-style: italic; } .fnum { color: #000; font-weight: bold; background: #dedede; } .ptr { color: #000; font-weight: bold; background: #dedede; } .fetch { color: #000; font-style: italic; background: #dedede; } .store { color: #000; font-style: italic; background: #dedede; } .char { color: #000; background: #dedede; } .inst { color: #000; background: #dedede; } .defer { color: #000; background: #dedede; } .imm { color: #000; font-weight: bold; background: #dedede; } .prim { color: #000; font-weight: bolder; background: #dedede; } .tt { white-space: pre; font-family: monospace; background: #dedede; } .h1, .h2, .h3, .h4 { white-space: normal; } .h1 { font-size: 125%; } .h2 { font-size: 120%; } .h3 { font-size: 115%; } .h4 { font-size: 110%; } .hr { display: block; height: 2px; background: #000000; }