From 22a344b4e1a2e31b690d819b661f8180343f097c Mon Sep 17 00:00:00 2001 From: crc Date: Fri, 21 Feb 2020 19:44:33 +0000 Subject: [PATCH] updates to the examples, better html exports now working FossilOrigin-Name: 67b765255e8896288380bce2b3b1203b070e29b04121de31ad9b5aa6007319e4 --- example/1D-Cellular-Automota.retro | 21 +- example/7080.retro | 63 ++++- example/99Bottles.retro | 17 ++ example/DictionaryAlias.retro | 2 +- example/FloatVar.retro | 10 +- example/conways-life.retro | 2 +- example/export-as-html.retro | 436 ++++++++++++++++++++++------- example/json.retro | 14 +- example/publish-examples.retro | 2 +- 9 files changed, 423 insertions(+), 144 deletions(-) diff --git a/example/1D-Cellular-Automota.retro b/example/1D-Cellular-Automota.retro index 6bce6a0..52e9b93 100644 --- a/example/1D-Cellular-Automota.retro +++ b/example/1D-Cellular-Automota.retro @@ -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`. ~~~ diff --git a/example/7080.retro b/example/7080.retro index e142fc4..eba022c 100755 --- a/example/7080.retro +++ b/example/7080.retro @@ -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 '%s s:format s:put ] case - $1 [ 'DIR_ s:put selector port server '%s s:format s:put ] case + $0 [ 'TXT_ s:put selector port server + '%s s:format s:put ] case + $1 [ 'DIR_ s:put selector port server + '%s 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 + '%s s:format s:put ] case + $h [ 'HTM_ s:put selector #4 + + '%s 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 + ' s:put + call + '
s:put nl ; + +:fields (s-a) + ASCII:HT s:tokenize dup !Line ; + +:menu-entry + [ @Line #0 a:fetch type indicate ] tt ; + +:plain-text + @Line [ ' s:put s:put ' s:put ] a:for-each nl ; -:tt ' s:put call '
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 [ ' s:put s:put ' 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 ' 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; ' s:put &Buffer s:put ' 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 +``` diff --git a/example/99Bottles.retro b/example/99Bottles.retro index f78f054..10c91a2 100644 --- a/example/99Bottles.retro +++ b/example/99Bottles.retro @@ -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 diff --git a/example/DictionaryAlias.retro b/example/DictionaryAlias.retro index fa43537..67a2eca 100644 --- a/example/DictionaryAlias.retro +++ b/example/DictionaryAlias.retro @@ -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. diff --git a/example/FloatVar.retro b/example/FloatVar.retro index cb39f5c..6326101 100644 --- a/example/FloatVar.retro +++ b/example/FloatVar.retro @@ -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 diff --git a/example/conways-life.retro b/example/conways-life.retro index b6b84e9..f828591 100644 --- a/example/conways-life.retro +++ b/example/conways-life.retro @@ -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 diff --git a/example/export-as-html.retro b/example/export-as-html.retro index bebab2a..5518186 100755 --- a/example/export-as-html.retro +++ b/example/export-as-html.retro @@ -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 - $< [ '< s:put ] case - $> [ '> s:put ] case +' s:put nl +~~~ + +Locate and embed the CSS from the end of this file. + +~~~ +' s:put nl +~~~ + +Finish the header boilerplate text and switch to the 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 + $< [ '< s:put ] case + $> [ '> s:put ] case $& [ '& s:put ] case + ASCII:SPACE [ '  s:put ] case c:put ; -:s:put-html [ c:put-html ] s:for-each ; + +:s:put [ c:put ] 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? [ ' &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 + 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: + ~~~ {{ - :span ' s:put s:put-html ' s:put ; - 'Fenced var - :toggle-fence @Fenced not !Fenced ; - :fenced? (-f) @Fenced ; - :handle-line (s-) - fenced? [ over call ] [ span '
s:put nl ] choose ; + 'Block var ---reveal--- - :unu (sq-) - swap [ dup '~~~ s:eq? - [ toggle-fence s:put '
s:put nl ] - [ handle-line ] choose - ] file:for-each-line drop ; + :in-code-block? (-f) @Block ; + :code-block? (s-sf) dup '~~~ s:eq? ; + + :begin '~~~ ; + :end '~~~ ; + :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 ' s:put s:put-html '_ 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? [ '  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 '``` ; + :end '``` ; + :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 - ' s:put nl ; +{{ + :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 ; -:header - ' s:put nl - ' s:put nl - ' s:put nl embed-css ' s:put nl ; + :colorize + ASCII:SPACE s:tokenize &format-code a:for-each ; -:body (s-) - ' s:put nl - [ ASCII:SPACE s:tokenize [ format ] a:for-each - '
s:put nl ] unu - ' s:put nl ; - -:footer - ' s:put nl ; - -:generate (s-) - header body footer ; + :format:code + ' s:put colorize ' 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 + '# [ '

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 ; ~~~ -# 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 + ' 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? ; +~~~ + +*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 '

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 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; } diff --git a/example/json.retro b/example/json.retro index 819c420..263023c 100644 --- a/example/json.retro +++ b/example/json.retro @@ -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: diff --git a/example/publish-examples.retro b/example/publish-examples.retro index a6ab887..6a76524 100755 --- a/example/publish-examples.retro +++ b/example/publish-examples.retro @@ -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 -~~~