9e767e0a10
FossilOrigin-Name: 3f2761d7ce1c4d062e4dddc57a8908989acc900355f6822313ffa599d3bb7d04
408 lines
9.2 KiB
Forth
Executable file
408 lines
9.2 KiB
Forth
Executable file
#! /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.
|
|
|
|
~~~
|
|
'<html><head> s:put nl
|
|
~~~
|
|
|
|
Locate and embed the CSS from the end of this file.
|
|
|
|
~~~
|
|
'<style> s:put nl
|
|
FALSE script:name
|
|
[ over [ '##_CSS s:eq? or ] -if; s:put nl ] file:for-each-line
|
|
drop
|
|
'</style> s:put nl
|
|
~~~
|
|
|
|
Finish the header boilerplate text and switch to the body.
|
|
|
|
~~~
|
|
'</head><body> s:put nl
|
|
~~~
|
|
|
|
### 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<code>
|
|
$< [ '< s:put ] case
|
|
$> [ '> s:put ] case
|
|
$& [ '& s:put ] case
|
|
ASCII:SPACE [ ' s:put ] case
|
|
c:put ;
|
|
|
|
:s:put<code> [ c:put<code> ] s:for-each ;
|
|
~~~
|
|
|
|
For regular text, there are a couple of inline formatting things
|
|
to deal with.
|
|
|
|
~~~
|
|
'Emphasis var
|
|
'Strong var
|
|
'Escape var
|
|
'Code var
|
|
|
|
:format
|
|
$` [ @Escape [ &Escape v:off $* c:put ] if;
|
|
@Code n:zero? [ '<tt_style='display:inline'> &Code v:on ]
|
|
[ '</tt> &Code v:off ] choose s:put ] case
|
|
$* [ @Escape @Code or [ &Escape v:off $* c:put ] if;
|
|
@Strong n:zero? [ '<strong> &Strong v:on ]
|
|
[ '</strong> &Strong v:off ] choose s:put ] case
|
|
$_ [ @Escape @Code or [ &Escape v:off $_ c:put ] if;
|
|
@Emphasis n:zero? [ '<em> &Emphasis v:on ]
|
|
[ '</em> &Emphasis v:off ] choose s:put ] case
|
|
$\ [ &Escape v:on ] case
|
|
c:put ;
|
|
|
|
:s:put<formatted> [ 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:
|
|
|
|
<div class='codeblock'><tt>
|
|
... code ...
|
|
</tt></div>
|
|
|
|
The actual words in the code will be in `<span>` 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 '<div_class='codeblock'><tt>~~~</tt> ;
|
|
:end '<tt>~~~</tt></div> ;
|
|
---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 '<div_class='codeblock'><tt>```</tt> ;
|
|
:end '<tt>```</tt></div> ;
|
|
---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 sigil, then by word class via
|
|
a little quick introspection.
|
|
|
|
~~~
|
|
{{
|
|
:span (s-)
|
|
'<span_class=' s:put s:put ''> s:put s:put<code> '</span>_ s:put ;
|
|
---reveal---
|
|
:format-code (s-)
|
|
(ignore_empty_tokens)
|
|
dup s:length n:zero? [ ' s:put drop ] if;
|
|
|
|
(tokens_with_sigils)
|
|
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<code> sp ;
|
|
|
|
:colorize
|
|
ASCII:SPACE s:tokenize &format-code a:for-each ;
|
|
|
|
:format:code
|
|
'<tt> s:put colorize '</tt> 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/char
|
|
'# [ '<h1> s:put n:inc s:put '</h1> s:put nl ] s:case
|
|
'## [ '<h2> s:put n:inc s:put '</h2> s:put nl ] s:case
|
|
'### [ '<h3> s:put n:inc s:put '</h3> s:put nl ] s:case
|
|
'#### [ '<h4> s:put n:inc s:put '</h4> 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
|
|
'<tt_class='indentedcode'> s:put
|
|
#4 + s:put<code>
|
|
'</tt> 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 '<hr> 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<formatted> '<br> s:put nl ;
|
|
|
|
:indented-list?
|
|
dup [ '__-_ s:begins-with? ] [ '__*_ s:begins-with? ] bi or ;
|
|
:format:indented-list
|
|
'<span_class='indentedlist'>• s:put
|
|
#3 + s:put<formatted> '</span><br> s:put nl ;
|
|
~~~
|
|
|
|
*Paragraphs*
|
|
|
|
Blank lines denote paragraph breaks.
|
|
|
|
~~~
|
|
:blank? dup s:length n:zero? ;
|
|
~~~
|
|
|
|
*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 '<p> 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<formatted> nl ;
|
|
|
|
#0 script:get-argument [ &Heap &format v:preserve ] file:for-each-line
|
|
reset
|
|
~~~
|
|
|
|
This concludes the Markdown (subset) in RETRO utility. All that's
|
|
left is the CSS.
|
|
|
|
For the colors, I'm mostly using the _Tomorrow Night_ colors as
|
|
listed at https://github.com/chriskempson/tomorrow-theme
|
|
|
|
## CSS
|
|
|
|
* {
|
|
color: #cccccc;
|
|
background: #2d2d2d;
|
|
max-width: 700px;
|
|
}
|
|
|
|
tt, pre {
|
|
background: #1d1f21; color: #b5bd68; font-family: monospace;
|
|
white-space: pre;
|
|
display: block;
|
|
width: 100%;
|
|
}
|
|
|
|
.indentedcode {
|
|
margin-left: 2em;
|
|
margin-right: 2em;
|
|
}
|
|
|
|
.codeblock {
|
|
background: #1d1f21; color: #b5bd68; 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;
|
|
}
|
|
|
|
.indentedlist {
|
|
margin-left: 2em;
|
|
color: #cccccc;
|
|
background: #2d2d2d;
|
|
}
|
|
|
|
span { white-space: pre; background: #1d1f21; }
|
|
.text { color: #c5c8c6; white-space: pre }
|
|
.colon { color: #cc6666; }
|
|
.note { color: #969896; }
|
|
.str { color: #f0c674; }
|
|
.num { color: #8abeb7; }
|
|
.fnum { color: #8abeb7; font-weight: bold; }
|
|
.ptr { color: #b294bb; font-weight: bold; }
|
|
.fetch { color: #b294bb; }
|
|
.store { color: #b294bb; }
|
|
.char { color: #81a2be; }
|
|
.inst { color: #de935f; }
|
|
.defer { color: #888888; }
|
|
.imm { color: #de935f; }
|
|
.prim { color: #b5bd68; font-weight: bold; }
|