updates to the examples, better html exports now working

FossilOrigin-Name: 67b765255e8896288380bce2b3b1203b070e29b04121de31ad9b5aa6007319e4
This commit is contained in:
crc 2020-02-21 19:44:33 +00:00
parent 4bf3b26d3b
commit 22a344b4e1
9 changed files with 423 additions and 144 deletions

View file

@ -6,14 +6,14 @@ Cells in the next generation of the array are calculated based on the value of t
If, in the following table, a live cell is represented by 1 and a dead cell by 0 then to generate the value of the cell at a particular index in the array of cellular values you use the following table:
000 -> 0 #
001 -> 0 #
010 -> 0 # Dies without enough neighbours
011 -> 1 # Needs one neighbour to survive
100 -> 0 #
101 -> 1 # Two neighbours giving birth
110 -> 1 # Needs one neighbour to survive
111 -> 0 # Starved to death.
000 -> 0 #
001 -> 0 #
010 -> 0 # Dies without enough neighbours
011 -> 1 # Needs one neighbour to survive
100 -> 0 #
101 -> 1 # Two neighbours giving birth
110 -> 1 # Needs one neighbour to survive
111 -> 0 # Starved to death.
I had originally written an implementation of this in RETRO 11. For RETRO 12 I took advantage of new language features and some further considerations into the rules for this task.
@ -89,16 +89,11 @@ The `record` word adds the evolved value to a buffer. In this case my `generatio
And now to tie it all together. Meet `generation`, the longest bit of code in this sample. It has several bits:
- setup a new buffer pointing to `Next`
- this also preserves the old buffer
- setup a loop for each cell in `This`
- initial loop index at -1, to ensure proper dummy state for first cell
- get length of `This` generation
- perform a loop for each item in the generation, updating `Next` as it goes
- copy `Next` to `This` using `update`.
~~~

View file

@ -13,15 +13,13 @@ functional enough to be useful to me.
To use:
- replace all instances of HTTP-DOMAIN with the server URL.
(e.g., http://server.tld:port)
- setup inetd to run this
- visit http://server.tld:port/gopher-url
e.g., for floodgap:
E.g., for floodgap:
http://server.tld:port/gopher.floodgap.com/1/
~~~
{ 'Server 'Port 'Selector 'Response 'Size } [ var ] a:for-each
@ -70,12 +68,19 @@ so I can remove the need for something other than RETRO.
[ dup file:read 0; buffer:add ] times unix:pclose
buffer:start s:length ] buffer:preserve ;
:http-prefix? (-f)
&Requested 'http:// s:begins-with? ;
:request (-)
&Requested 'http:// s:begins-with? [ &Requested #7 + ] [ &Requested n:inc ] choose
http-prefix? [ &Requested #7 + ] [ &Requested n:inc ] choose
'gopher:// s:prepend
&Buffer FREE net:fetch &Buffer !Response !Size ;
~~~
Parsing the URL to the various internal variables is a matter
of splitting the string, saving the pieces, and storing the
pointers in the variables.
~~~
:parse-url (s-)
$: s:split s:keep !Server
@ -100,10 +105,17 @@ so I can remove the need for something other than RETRO.
:server @Line #2 a:fetch ;
:selector @Line #1 a:fetch ;
:description @Line #0 a:fetch ;
~~~
The `indicate` word displays a short string of three characters
that provide a hint to the user about the content being linked.
~~~
:indicate
$0 [ 'TXT_ s:put selector port server '<a_href="HTTP-DOMAIN/%s:%s/0%s">%s</a> s:format s:put ] case
$1 [ 'DIR_ s:put selector port server '<a_href="HTTP-DOMAIN/%s:%s/1%s">%s</a> s:format s:put ] case
$0 [ 'TXT_ s:put selector port server
'<a_href="HTTP-DOMAIN/%s:%s/0%s">%s</a> s:format s:put ] case
$1 [ 'DIR_ s:put selector port server
'<a_href="HTTP-DOMAIN/%s:%s/1%s">%s</a> s:format s:put ] case
$2 [ 'CSO_ s:put s:put ] case
$3 [ 'ERR_ s:put s:put ] case
$4 [ 'BHX_ s:put s:put ] case
@ -112,17 +124,33 @@ so I can remove the need for something other than RETRO.
$7 [ 'FND_ s:put s:put ] case
$8 [ 'TEL_ s:put s:put ] case
$9 [ 'BIN_ s:put s:put ] case
$h [ 'HTM_ s:put selector #4 + '<a_href="%s">%s</a> s:format s:put ] case
$h [ 'HTM_ s:put selector #4 +
'<a_href="%s">%s</a> s:format s:put ] case
$I [ 'IMG_ s:put s:put ] case
$g [ 'GIF_ s:put s:put ] case
$i [ '____ s:put s:put ] case
drop 'UNK_ s:put description s:put drop ;
~~~
~~~
:tt
'<tt_style='white-space:_pre'> s:put
call
'</tt><br> s:put nl ;
:fields (s-a)
ASCII:HT s:tokenize dup !Line ;
:menu-entry
[ @Line #0 a:fetch type indicate ] tt ;
:plain-text
@Line [ '<tt> s:put s:put '</tt> s:put ] a:for-each nl ;
:tt '<tt_style='white-space:_pre'> s:put call '</tt><br> s:put nl ;
:line
dup ASCII:HT s:contains-char?
[ ASCII:HT s:tokenize !Line @Line a:length #3 gteq? [ [ @Line #0 a:fetch type indicate ] tt ]
[ @Line [ '<tt> s:put s:put '</tt> s:put ] a:for-each nl ] choose ] if;
[ fields a:length #3 gteq? [ [ menu-entry ]
[ plain-text ] choose ] if;
[ s:putm ] tt ;
:css
@ -131,15 +159,26 @@ so I can remove the need for something other than RETRO.
'a_{_color:_orange;_} s:put nl
'</style> s:put nl ;
:process:line
&Buffer ASCII:CR s:tokenize
[ s:trim line ] a:for-each ;
:process
&Buffer ASCII:HT s:contains-char? [ &Buffer ASCII:CR s:tokenize [ s:trim line ] a:for-each ] if;
&Buffer ASCII:HT s:contains-char? [ process:line ] if;
'<xmp> s:put &Buffer s:put '</xmp> s:put ;
:eol ASCII:CR c:put ASCII:LF c:put ;
:headers 'HTTP/1.1_200_OK s:put eol 'Content-Type:_text/html s:put eol eol ;
:headers 'HTTP/1.1_200_OK s:put eol
'Content-Type:_text/html s:put eol eol ;
:7080 read request headers css process ;
7080
~~~
If you want to log the requests, this optional bit stores the
latest request in a file.
```
&Requested '/home/crc/retro/proxy.log file:spew
```

View file

@ -2,11 +2,23 @@
Display the text for the *99 Bottles of Beer* song.
For this, I'm using `s:evaluate` to construct words which
display a string when called. This lets the majority of the
code read nicely.
~~~
{ 'bottle 'bottles 'of 'beer 'on 'the 'wall 'no 'more
'Take 'one 'down, 'pass 'it 'around }
[ dup ':%s_'%s_s:put_sp_; s:format s:evaluate ] a:for-each
~~~
Handling of the exact text related to the number of bottles
is done with a simple array of functions that get selected
based on the number of bottles left. This is done with a
very simple filter, where the number of bottles for the
purpose of the text is in a set of 2 or more, 1, or none.
~~~
{ [ no more bottles ]
[ #1 n:put sp bottle ]
[ dup n:put sp bottles ]
@ -14,7 +26,12 @@ Display the text for the *99 Bottles of Beer* song.
:number-bottles
dup #2 n:min BOTTLES swap a:fetch call ;
~~~
Thanks to the programatically generated words for the
verse text, the main code is nicely readable.
~~~
:display-verse
number-bottles of beer on the wall nl
number-bottles of beer nl

View file

@ -1,6 +1,6 @@
The easiest way to make an alias is by
:s2 s1 ;
:s2 s1 ;
which adds a layer of `call`.
`d:alias` eliminates this overhead.

View file

@ -149,22 +149,24 @@ two integers occupying two 32-bit cells.
Two different ways are implemented:
- `w1` uses fixed point encoding.
Splits a floating point number into the integer and the fraction
parts to store them in two cells.
This is faster with a narrower dynamic range.
- `w2` uses sqrt-encoding described in doc/SqrtEncoding.pdf .
This is slower with a relatively wider dynamic range,
but likely not as wide as the original floating point.
Since Retro's cells are 32-bit,
it is more convenient to handle floating point numbers
encoded into single cells than into pairs of cells.
For instance, consider using the `a:` words for vectors.
Since Retro's cells are 32-bit, it is more convenient to handle
floating point numbers encoded into single cells than into pairs
of cells. For instance, consider using the `a:` words for vectors.
Hence in many cases the words found by doing
'e: d:words-with
are suitable.
However, sometimes a higher precision is desired at a higher cost.
These are the same as `f:+encode` and `f:-encode` except that they come

View file

@ -26,7 +26,7 @@ the discrete moment at which this happens is sometimes called a tick.
Each generation is a pure function of the preceding one. The rules
continue to be applied repeatedly to create further generations.
Taken from https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
Taken from `https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life`
# The Code

View file

@ -1,139 +1,365 @@
#!/usr/bin/env retro
#! /usr/bin/env retro
# Export as HTML
# RETRO Source to HTML
This tool processes the code in Unu code blocks, generating HTML
output. It assumes that CSS will be used to style the results.
The default CSS is included at the end of this file and will be
embedded into the generated HTML.
RETRO programs are written using a literate format called
Unu which allows mixing of code and commentary in a somewhat
literate format.
# Code
Code (and optionally tests) is extracted from fenced blocks,
and commentary normally uses a subset of Markdown.
I include my `unu` file processor here as it makes it easy to
process the relevant lines from the source file.
This tool processes both parts, generating formatted HTML
documents that look nice, and also provides syntax highlighting
for the test and code blocks.
Some characters need to be escaped. The `:put-html` words will
be used to handle these.
## 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 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.
~~~
:c:put-html
$< [ '&lt; s:put ] case
$> [ '&gt; s:put ] case
'<html><head> s:put nl
~~~
Locate and embed the CSS from the end of this file.
~~~
'<style> s:put nl
FALSE sys: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>
$< [ '&lt; s:put ] case
$> [ '&gt; s:put ] case
$& [ '&amp; s:put ] case
ASCII:SPACE [ '&nbsp; s:put ] case
c:put ;
:s:put-html [ c:put-html ] s:for-each ;
: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:on ] if;
$` [ @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
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:
~~~
{{
:span '<span_class='text'> s:put s:put-html '</span> s:put ;
'Fenced var
:toggle-fence @Fenced not !Fenced ;
:fenced? (-f) @Fenced ;
:handle-line (s-)
fenced? [ over call ] [ span '<br> s:put nl ] choose ;
'Block var
---reveal---
:unu (sq-)
swap [ dup '~~~ s:eq?
[ toggle-fence s:put '<br> s:put nl ]
[ handle-line ] choose
] file:for-each-line drop ;
:in-code-block? (-f) @Block ;
:code-block? (s-sf) dup '~~~ s:eq? ;
:begin '<div_class='codeblock'><tt>~~~</tt> ;
:end '<tt>~~~</tt></div> ;
:toggle-code (n-)
drop @Block n:zero? dup &begin &end choose s:put !Block ;
}}
~~~
Formatting is pretty straightforward. I have a `span` word to
generate the HTML for the token, and a `format` word to identify
the token type and use `span`.
And test blocks are basically the same, except for the
delimiters.
~~~
:span '<span_class=' s:put s:put ''> s:put s:put-html '</span>_ s:put ;
{{
'Block var
---reveal---
:in-test-block? (-f) @Block ;
:test-block? (s-sf) dup '``` s:eq? ;
:format (s-)
(ignore_empty_tokens)
dup s:length n:zero? [ '&nbsp; 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-html sp ;
:begin '<div_class='codeblock'><tt>```</tt> ;
:end '<tt>```</tt></div> ;
:toggle-test (n-)
drop @Block n:zero? dup &begin &end choose s:put !Block ;
}}
~~~
Next is the HTML page generation. This has words to generate
the header, embeds the CSS (from the end of this file), and
then processes the lines in the source file to generate the
body.
Output is written to stdout.
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.
~~~
'CSS var
:embed-css
'<style> s:put nl
sys:name [ dup '/*_CSS_Begins_*/ s:eq? [ &CSS v:on ] if
@CSS [ s:put nl ] [ drop ] choose ] file:for-each-line
'</style> s:put nl ;
{{
: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? [ '&nbsp; s:put drop ] if;
:header
'<!DOCTYPE_html> s:put nl
'<html> s:put nl
'<head> s:put nl embed-css '</head> s:put nl ;
(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
:body (s-)
'<body> s:put nl
[ ASCII:SPACE s:tokenize [ format ] a:for-each
'<br> s:put nl ] unu
'</body> s:put nl ;
(immediate_and_primitives)
drop dup
d:lookup d:class fetch
&class:macro [ 'imm span ] case
&class:primitive [ 'prim span ] case
drop
:footer
'</html> s:put nl ;
(normal_words)
s:put<code> sp ;
:generate (s-)
header body footer ;
:colorize
ASCII:SPACE s:tokenize &format-code a:for-each ;
:format:code
'<tt> s:put colorize '</tt> s:put nl ;
}}
~~~
And finally, run it.
*Headers*
After this, I define detection and formatting of headers. The
headers should look like:
# Level 1
## Level 2
### Level 3
~~~
#0 sys:argv generate
:header?
dup [ '# s:begins-with? ]
[ '## s:begins-with? ]
[ '### s:begins-with? ] tri or or ;
:format:head
ASCII:SPACE s:split
'# [ '<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
drop ;
~~~
# CSS
*Indented Code Blocks*
/* CSS Begins */
Indented code blocks are lines indented by four spaces.
These are *not* syntax highlighted as they are ignored by
Unu.
* { background: #1d1f21; color: #b5bd68; font-family: monospace; }
span { white-space: pre }
.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: #888; }
.imm { color: #de935f; }
.prim { color: #b5bd68; font-weight: bold; }
~~~
: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 '&bull;_ 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'>&bull; 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
in-code-block? [ format:code ] if;
in-test-block? [ format:code ] if;
code-block? [ toggle-code ] if;
test-block? [ toggle-test ] 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 sys:argv [ &Heap &format v:preserve ] file:for-each-line
reset
~~~
This concludes the Markdown (subset) in RETRO utility. All that's
left is the CSS.
## 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: #888; }
.imm { color: #de935f; }
.prim { color: #b5bd68; font-weight: bold; }

View file

@ -14,7 +14,7 @@ provide some access to JSON using RETRO.
With this, I can begin to parse JSON and do things with it.
Import a JSON test set (https://api.github.com/repos/stedolan/jq/commits?per_page=5)
Import a JSON test set (`https://api.github.com/repos/stedolan/jq/commits?per_page=5`)
~~~
'JSON d:create
@ -29,12 +29,12 @@ JSON '.[]_|_{message:_.commit.message}_|_flatten jq s:temp
This leaves output like:
[
"Improve jv_is_integer()"
]
[
"Dockerfile: Change base image to Debian Stable"
]
[
"Improve jv_is_integer()"
]
[
"Dockerfile: Change base image to Debian Stable"
]
I can tokenize this and filter out the [ ] pairs:

View file

@ -88,6 +88,6 @@ FILE-PATH 'gophermap s:append file:W file:open !FID
:glossary source? &drop -if;
FILE-PATH over
'retro-document_%s_>%s%s.glossary s:format unix:system $. c:put ;
~~~
[ dir? &drop &glossary choose ] unix:for-each-file nl
~~~