From b0b315622c8184ce725e88f70af8146661f4fe08 Mon Sep 17 00:00:00 2001 From: crc Date: Thu, 23 May 2024 20:00:14 +0200 Subject: [PATCH] source from 2023.10 release --- extend.konilo | 463 +++++++ konilo.pali | 3450 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 3913 insertions(+) create mode 100644 extend.konilo create mode 100644 konilo.pali diff --git a/extend.konilo b/extend.konilo new file mode 100644 index 0000000..2bc71b8 --- /dev/null +++ b/extend.konilo @@ -0,0 +1,463 @@ +================================================================ + + ,dPYb, ,dPYb, + IP'`Yb IP'`Yb + I8 8I gg I8 8I + I8 8bgg, "" I8 8' + I8 dP" "8 ,ggggg, ,ggg,,ggg, gg I8 dP ,ggggg, + I8d8bggP" dP" "Y8 ,8" "8P" "8, 88 I8dP dP" "Y8 + I8P' "Yb, i8' ,8I d8 8I 8I 88 I8P i8' ,8I +,d8 `Yb,,d8, ,d8P8P 8I Yb,_,88,_,d8b,_ ,d8, ,d8' +88P Y8P"Y8888P" 8I `Y88P""Y88P'"Y88P"Y8888P" + +================================================================ + +This file provides the high level extensions to the Konilo +system. + +Stack comments are made by the `(` sigil. I define this first, +as they are useful to have. + +~~~ +&sigil:( #40 sigil:set +:) ; +~~~ + +Next is a `$` sigil, to return the character from a string. I'll +use this to setup other sigils without needing to hard code the +ASCII constants. + +~~~ +:sigil:$ #0 s:fetch process-data ; +&sigil:$ #36 sigil:set +~~~ + +For accessing variables, the `@` (fetch) and `!` (store) sigils +are defined. + +~~~ +:sigil:@ + d:lookup d:address fetch + compiling? [ &internal:lit comma comma &fetch comma ] + &fetch choose ; + +:sigil:! + d:lookup d:address fetch + compiling? [ &internal:lit comma comma &store comma ] + &store choose ; + +&sigil:@ $@ sigil:set +&sigil:! $! sigil:set +~~~ + +The last sigil is `\`, which creates a header pointing to an +address. You can use this to name quotes: + + [ ... ] \name + +or to make an alias: + + &drop \sigil:( + +~~~ +:sigil:\ d:create @Dictionary d:address store ; +&sigil:\ $\ sigil:set +~~~ + +I/O device 7 pushes the depth of the data and address stacks to +the data stack. (Excluding the pushed values). I define words to +access these should the need arise. (Mostly, I just use the +`depth/data` to make sure the data stack has the right number +of elements) + +~~~ +:depths (-nn) #7 io ; (data,_then_address_depth) +:depth/data (-n) depths drop ; +:depth/address (-n) depths nip ; +~~~ + +Scope words. I've used these for over a decade now. It lets me +hide factors from the main dictionary. This isn't realy crucial, +just a quality of life helper. + +~~~ +:{{ @Dictionary dup &sys:buffers/scope store-next store ; +:---reveal--- + '_ d:create @Dictionary &sys:buffers/scope n:inc store ; +:}} &sys:buffers/scope fetch-next swap fetch d:link store ; +~~~ + +Variables. Ones made without an initial value are set to zero. +Use the & sigil when using them. + +~~~ +:var-n (ns-) d:create comma ; +:var (s-) d:create #0 comma ; +~~~ + +~~~ +:rot (abc-bca) &swap dip swap ; +:n:between? (nlu-f) rot [ rot rot n:limit ] sip eq? ; +~~~ + +The words in `compile:` are intended to help in writing compiler +extensions for generating ilo code words. I use them to implement +`curry`, which binds a value and function together into a new +function. + +E.g., + + #678 &n:put curry \display:678 + +~~~ +:compile:lit (n-) #1 comma comma ; +:compile:call (a-) compile:lit #8 comma ; +:compile:jump (a-) compile:lit #7 comma ; + +:curry (vq-q) here [ swap compile:lit compile:jump ] dip ; +~~~ + +Arrays are important, being the data structure used for strings +and a variety of other things. This is a very simple way to +create them. + +~~~ +:a:make (...n-a) here [ dup comma &comma times ] dip ; +:a:make/temp (...n-a) [ a:make a:temp ] gc ; +~~~ + +`a:reduce` will iterate over an array and value, applying a +function to reduce the array down to a single value. For instance, +to sum an array: + + &array #0 &n:add a:reduce + +~~~ +:a:reduce (anq-n) &swap dip a:for-each ; +~~~ + +To map a function to each value in an array, I provide `a:map`. +This modifies the original array, not a copy of it. + +~~~ +:a:map (aq-a) + swap [ fetch-next [ [ fetch over call ] sip + &store sip n:inc ] times + drop-pair ] sip ; + +&a:map \s:map +~~~ + +The next couple of words allow for extracting portions of an +array into a new array. The subsets will be stored in the +temporary buffers. + +~~~ +:a:middle (afl-a) + here [ dup comma [ n:inc n:add ] dip + here swap copy ] dip dup &Free store s:temp ; + +:a:left (an-a) #0 swap a:middle ; +:a:right (an-a) over s:length over n:sub swap a:middle ; + +&a:left \s:left (sn-s) +&a:right \s:right (sn-s) +&a:middle \s:middle (sfl-s) +~~~ + +Input tokens are whitespace delimited. This presents an issue +for strings: how to add spaces? + +The solution used here is to replace (via `s:map`) underscores +with spaces. I've been doing this now for many years, and it's +not proven to be a problem. The `sigil:'` will use the latest +`s:rewrite` it can find, so you can redefine this to make other +changes if you need to. + +~~~ +:s:rewrite (s-) [ dup $_ eq? [ drop #32 ] if ] s:map ; +~~~ + +`a:indices` returns an array with the locations of a value that +matches the passed value. `a:index` returns the first location +that matches. + +~~~ +{{ + 'Count var + :prepare #0 &Count store ; + :reserve swap #0 comma ; + :patch here over n:sub n:dec over store ; + :cleanup dup s:temp swap &Free store ; + :record &Count fetch comma ; + :iterate [ (match? over eq? ) &record if + &Count v:inc ] a:for-each ; +---reveal--- + :a:indices (av-a) + prepare here [ reserve iterate drop ] dip patch cleanup ; +}} + +:a:index (av-n) [ a:indices #0 a:fetch ] gc ; +&a:index \s:index/c +~~~ + +`a:contains?` returns a flag indicating whether or not an array +contains a given value. + +~~~ +:a:contains? (an-f) + swap #0 swap [ swap [ over eq? ] dip or ] a:for-each nip ; + +&a:contains? \s:contains? (sc-f) +~~~ + +`a:filter` runs a quote against each value in an array. The quote +needs to consume the value and return a single flag. If true, the +value is added to a new array. + +~~~ +:a:filter (aq-) + [ [ over &call dip swap &comma &drop choose ] curry + here [ over fetch comma a:for-each ] dip + here over n:sub n:dec over store a:temp ] gc ; + +&a:filter \s:filter (sq-) +~~~ + +~~~ +:c:lowercase? (c-f) $a $z n:between? ; +:c:uppercase? (c-f) $A $Z n:between? ; +:c:to-upper (c-c) dup c:lowercase? [ #32 n:sub ] if ; +:c:to-lower (c-c) dup c:uppercase? [ #32 n:add ] if ; +:c:to-s (c-s) '_ s:temp tuck #0 s:store ; + +:s:to-upper (s-s) [ s:dup &c:to-upper s:map s:temp ] gc ; +:s:to-lower (s-s) [ s:dup &c:to-lower s:map s:temp ] gc ; +~~~ + +~~~ +{{ + 'Current var + :get-index &sys:buffers/loops @Current n:add ; + :prepare &Current v:inc #0 get-index store ; + :cleanup &Current v:dec ; + :inner:indexed-times + swap [ dup &call dip (next get-index v:inc ) ] dip n:dec + tuck n:-zero? &inner:indexed-times ?jump drop-pair ; +---reveal--- + :I (-n) get-index fetch ; + :J (-n) get-index #1 n:sub fetch ; + :K (-n) get-index #2 n:sub fetch ; + :indexed-times (nq-) prepare inner:indexed-times cleanup ; +}} + +:bi (xqq-) &sip dip call ; +:bi* (xyqq-) &dip dip call ; +:bi@ (xyq-) dup bi* ; +:tri (xqqq-) [ &sip dip sip ] dip call ; +:tri* (xyzqqq-) [ [ swap &dip dip ] dip dip ] dip call ; +:tri@ (xyzq-) dup dup tri* ; + +:a:eq? (aa-f) &a:hash bi@ eq? ; +&a:eq? \s:eq? (ss-f) + +:a:-eq? (aa-a) a:eq? not ; +&a:-eq? \s:-eq? (ss-f) + +:a:chop (a-a) a:temp &v:dec sip ; +&a:chop \s:chop (s-s) + +:a:behead (a-a) + a:chop [ [ n:inc dup n:inc swap ] + &a:length bi copy ] sip ; +&a:behead \s:behead (s-s) + +:a:first (a-n) #0 a:fetch ; +:a:last (a-n) dup a:length n:dec a:fetch ; + +&a:first \s:first (s-c) +&a:last \s:last (s-c) +~~~ + +The `s:trim` words remove leading, or trailing (or both) +whitespace from a string. These aren't mirrored in the `a:` +namespace. + +~~~ +:s:trim-right (s-s) + s:temp [ dup v:dec [ s:last #32 lteq? ] sip swap ] while + dup v:inc ; + +{{ + 'Start var + 'End var + 'Len var + :find-end dup s:length dup !Len over n:add n:inc !End n:inc ; + :new-size @Start over swap n:sub @Len swap n:sub ; +---reveal--- + :s:trim-left (s-s) + s:dup dup #0 s:fetch #32 eq? + [ dup !Start find-end + [ fetch-next #32 -eq? over @End -eq? and ] while + new-size (patch over store ) ] if ; +}} + +:s:trim (s-s) s:trim-left s:trim-right ; +~~~ + +`n:get` reads a number from the input device. + +~~~ +:n:get (-n) s:get/token s:temp s:to-n ; +~~~ + +================================================================ + +The block editor is an important piece of the Konilo system. +It's how code is entered, managed, and run. + +~~~ +:block:buffer (-a) &sys:buffers/block ; + +#16 'Blocks var-n (number_of_blocks_available) +#0 'Block var-n (current_block_number) +~~~ + +~~~ +:e:to-line (n-a) #64 n:mul block:buffer n:add ; +:e:line (n-) e:to-line #64 [ fetch-next c:put ] times drop nl ; + +{{ + :sep sp sp sp #6 [ '+----5---- s:put ] times '+--- s:put nl ; + :l/n I dup #10 lt? &sp if n:put sp ; + :line l/n I e:line ; + :lines #16 &line indexed-times ; + :info 'sys:info d:lookup dup n:-zero? + [ d:address fetch call ] &drop choose ; +---reveal--- + :list* (-) nl #16 [ I e:line ] indexed-times ; + :list# (-) nl lines ; + :list (-) nl sep lines sep info ; +}} + +&list 'e:Display var-n +{{ + :reset #1024 block:buffer n:dec store ; + :constrain @Block #0 @Blocks n:dec n:limit !Block ; +---reveal--- + :set (n-) !Block constrain ; + :save (-) @Block block:buffer block:save ; + :load (-) @Block block:buffer block:load reset ; + :next (-) &Block v:inc constrain load ; + :prev (-) &Block v:dec constrain load ; + :new (-) #32 block:buffer #1024 fill reset ; + :edit (n-) set load @e:Display call ; + :run (-) reset block:buffer n:dec s:evaluate ; + :use (block) set load run ; + :using (first,last) over n:sub swap use [ next run ] times ; +}} + +{{ + :handle dup #8 eq? over #127 eq? or + [ drop #-1 allot ] &comma choose ; + :process dup #10 eq? [ drop #-1 ] [ handle #0 ] choose ; +---reveal--- + :s:get/line (-s) + here [ #0 comma [ c:get process ] until ] sip + here over n:sub n:dec over !Free swap store drop + here s:temp ; +}} + +:e:erase/line (n-) e:to-line #32 swap #64 fill ; +:e:replace (sn-) n:inc swap e:to-line over n:dec s:length copy ; +:e:replace-at (snn-) [ &e:to-line dip n:add ] dip + [ over store n:inc ] s:for-each drop ; + +:e:insert (n"-) dup e:erase/line s:get/line e:replace ; +:e:insert-at (nn"-) s:get/line e:replace-at ; + +:0 ("-) #0 e:insert ; :1 ("-) #1 e:insert ; +:2 ("-) #2 e:insert ; :3 ("-) #3 e:insert ; +:4 ("-) #4 e:insert ; :5 ("-) #5 e:insert ; +:6 ("-) #6 e:insert ; :7 ("-) #7 e:insert ; +:8 ("-) #8 e:insert ; :9 ("-) #9 e:insert ; +:10 ("-) #10 e:insert ; :11 ("-) #11 e:insert ; +:12 ("-) #12 e:insert ; :13 ("-) #13 e:insert ; +:14 ("-) #14 e:insert ; :15 ("-) #15 e:insert ; +~~~ + +~~~ +:. ("-) #62 [ c:get drop ] times ; +~~~ + +The basic `use` and `using` words aren't very convienient as you +need to keep track of the exact blocks to load. `needs` allows +you to use the first part of a block title line (typically a +comment for code blocks) instead. It'll scan through the block +set, running any blocks that match the provided text. E.g., to +load a block set named "(pali)": + + '(pali) needs + +Blocks are loaded in order from 0 to N, where N is the value in +`Blocks`. This can be very slow if you have a large block set or +are loading multiple sets of blocks. You may want to consider +keeping blocks towards the low end of the block set and limiting +`Blocks` before running this. + +~~~ +{{ + 'Len var + :buffer (-a) &sys:buffers/needs ; + :check (-f) block:buffer buffer @Len compare ; + :setup (s-) dup s:length !Len n:inc buffer @Len copy ; +---reveal--- + :needs (s-) + setup @Block [ + @Blocks [ I set load check &run if ] indexed-times + ] dip !Block load ; +}} +~~~ + +For generating an index of the blocks, `titles` is provided. +This shows the block number and index line for any block with +a title line. + +~~~ +:titles (-) + @Block @Blocks + [ I set load block:buffer fetch #32 -eq? + [ I n:put sp #64 block:buffer n:dec &store &s:put bi nl ] if + ] indexed-times !Block load ; +~~~ + +`sys:info` is called by the editor. It displays a status line below +the editor output. You can write a new `sys:info`, and the the code +will use the most recent one. + +~~~ +:sys:info (-) + '___B: s:put @Block n:put $/ c:put @Blocks n:dec n:put + '___S: s:put depth/data n:put $/ c:put #32 n:put + '___M: s:put here n:put $/ c:put #59999 n:put nl ; +~~~ + +================================================================ + +The last couple of things are just to save the image and set a +startup word. The default startup word runs the code in blocks +1 and 2, then starts the editor on block 0. + +~~~ +{{ + :process (n-f) set load block:buffer fetch $( eq? &run if ; +---reveal--- + :prelude (-) #1 process #2 process ; + :startup (-) prelude ; +}} + +:rom:save (-) #4 io ; + +rom:save bye +~~~ + diff --git a/konilo.pali b/konilo.pali new file mode 100644 index 0000000..faca56a --- /dev/null +++ b/konilo.pali @@ -0,0 +1,3450 @@ +================================================================ + + ,dPYb, ,dPYb, + IP'`Yb IP'`Yb + I8 8I gg I8 8I + I8 8bgg, "" I8 8' + I8 dP" "8 ,ggggg, ,ggg,,ggg, gg I8 dP ,ggggg, + I8d8bggP" dP" "Y8 ,8" "8P" "8, 88 I8dP dP" "Y8 + I8P' "Yb, i8' ,8I d8 8I 8I 88 I8P i8' ,8I +,d8 `Yb,,d8, ,d8P8P 8I Yb,_,88,_,d8b,_ ,d8, ,d8' +88P Y8P"Y8888P" 8I `Y88P""Y88P'"Y88P"Y8888P" + +================================================================ + +Copyright (c) charles childers + +================================================================ + +This is a small, pragmatic forth running on the ilo virtual +computer. The source is written in unu (see unu.retroforth.org), +a format for literate programming. + +Commentary is mixed throughout the code. I'll try to keep the +comments brief, describing the provided functions and adding in +notes where I think they are relevant. + +Let's take a brief moment to look at the source format. The code +is placed between fences (the ~~~ / ~~~) pairs. Anything outside +of these fences is commentary. In addition, I have comment lines +(starting with "c ") inside the code blocks. + +The code here is assembly, for the `pali` assembler. + +Pali assembly takes the form of: + + + +Directives are a single character. The length of the data part +varies. + +Directives are: + ++-----------+--------------------------------------------------+ +| Directive | Action | ++===========+==================================================+ +| : | data is a label | +| i | data is an instruction bundle | +| r | data is a reference to a label | +| - | data is a reference to a label | +| d | data is an integer (decimal) value | +| s | data is a string | +| c | data is a comment; ignored by pali | +| * | data is an integer, the number is the amount | +| | of space to allocate | ++-----------+--------------------------------------------------+ + +Instruction bundles are groups of four ilo instructions. The +instruction names are identified by two letter codes. These are +shown in the following tables: + + Opode Instruction Names Data Stack Effects + ===== ================= ==================================== + 00-05 .. li du dr sw pu - -n n-nn n- nm-mn n- + 06-11 po ju ca cc cj re -n a- a- af- af- - + 12-17 eq ne lt gt fe st nn-f nn-f nn-f nn-f a-n na- + 18-23 ad su mu di an or nn-n nn-n nn-nn nn-n nn-n nn-n + 24-29 xo sl sr cp cy io nn-n nn-n nn-n nnn- nnn- n- + ===== ================= ==================================== + +An important note: instructions that modify the instruction +pointer must only be followed by a non-op. These instructions +are: ju ca cc cj re + +================================================================ + +On startup ilo will load a memory image (the "rom") into ram and +jump to address 0. I call `setup:sigls` to setup the environment +and then jump into `forth` to begin actually handling the user +environment. + +ilo provides 65,536 memory words. For Konilo, these are +organized as: + ++---------------+----------------------------------------------+ +| Range | Used For | ++===============+==============================================+ +| 00000 - 59999 | forth & user code / data | +| 60000 - 61025 | block buffer | +| 61026 - 61091 | string evaluation buffers | +| 61092 - 61104 | loop indices | +| 61105 - 61139 | number conversion | +| 61140 - 61141 | lexical scope | +| 61142 - 61206 | needs buffer | +| 61207 - 61335 | sigils table | +| 61336 - 64377 | reserved for future system use | +| 64378 - 65406 | string buffers (8 x 127 chars) | +| 65407 - 65535 | text input buffer (127 chars) | ++---------------+----------------------------------------------+ + +I map names to these before proceeding. This will help keep +later code using these more readable. + +~~~ +c set to 1 after the actual start to leave room for a length +c cell +o 60001 +: sys:buffers/block + +o 60126 +: sys:buffers/string-eval + +o 61092 +: sys:buffers/loops + +o 61105 +: sys:buffers/numeric-conversion + +o 61140 +: sys:buffers/scope + +o 61142 +: sys:buffers/needs + +o 61207 +: Sigils +* 128 + +o 61336 +: sys:buffers/reserved + +o 64378 +: sys:buffers/strings+arrays + +o 65407 +: sys:buffers/input +o 65408 +: sys:buffers/input/text +o 65409 +: sys:buffers/input/text+1 +~~~ + +After creating the labels for the system buffers, return to +address 0 to start the actual code. + +On startup this calls a function to populate the default sigils, +then jumps into forth. + +~~~ +o 0 +i lica.... +r setup:sigils +i liju.... +r forth +~~~ + +================================================================ + +With the configuration out of the way, I move on to implementing +the various words. + +Being a Forth, functions or subroutines are called `words`. +Information about these is stored in a `dictionary`. + +The first set of words I'm defining map directly to the +instructions in the ilo computer. + +`dup` duplicates the top item on the data stack. + + +---+ +---+---+ + | 1 | ==> | 1 | 1 | + +---+ +---+---+ + +~~~ +: dup +c (n-nn) +i dure.... +~~~ + +`drop` discards the top item on the data stack. + + +---+---+ +---+ + | 1 | 2 | ==> | 1 | + +---+---+ +---+ + +~~~ +: drop +c (n-) +i drre.... +~~~ + +`swap` exchanges the positions of the top two values on the +data stack. + + +---+---+ +---+---+ + | 1 | 2 | ==> | 2 | 1 | + +---+---+ +---+---+ + +~~~ +: swap +c (nm-mn) +i swre.... +~~~ + +`eq?` compares two items on the stack for equality. If they are +equal, it will push a true (-1) flag. If they are not equal, it +pushes false (0) instead. + + +---+---+ +----+ + | 1 | 1 | ==> | -1 | + +---+---+ +----+ + + +---+---+ +----+ + | 2 | 1 | ==> | 0 | + +---+---+ +----+ + +~~~ +: eq? +c (nn-f) +i eqre.... +~~~ + +`-eq?` compares two items on the stack for inequality. If they +are not equal, it will push a true (-1) flag. If they are equal, +it pushes false (0) instead. + + +---+---+ +----+ + | 1 | 1 | ==> | 0 | + +---+---+ +----+ + + +---+---+ +----+ + | 2 | 1 | ==> | -1 | + +---+---+ +----+ + +~~~ +: neq? +c (nn-f) +i nere.... +~~~ + + +---+---+ +----+ + | 1 | 1 | ==> | 0 | + +---+---+ +----+ + + +---+---+ +----+ + | 2 | 1 | ==> | 0 | + +---+---+ +----+ + + +---+---+ +----+ + | 1 | 2 | ==> | -1 | + +---+---+ +----+ + +~~~ +: lt? +c (nn-f) +i ltre.... +~~~ + + +---+---+ +----+ + | 1 | 1 | ==> | 0 | + +---+---+ +----+ + + +---+---+ +----+ + | 2 | 1 | ==> | -1 | + +---+---+ +----+ + + +---+---+ +----+ + | 1 | 2 | ==> | 0 | + +---+---+ +----+ + +~~~ +: gt? +c (nn-f) +i gtre.... +~~~ + +~~~ +: fetch +c (p-n) +i fere.... +~~~ + +~~~ +: store +c (np-) +i stre.... +~~~ + +`n:add` adds two values, returning the result. + + +---+---+ +---+ + | 1 | 2 | ==> | 3 | + +---+---+ +---+ + +~~~ +: n:add +c (nn-n) +i adre.... +~~~ + +`n:sub` subtracts two values, returning the result. + + +---+---+ +---+ + | 3 | 1 | ==> | 2 | + +---+---+ +---+ + +~~~ +: n:sub +c (nn-n) +i sure.... +~~~ + +`n:mul` multiplies two values, returning the result. + + +---+---+ +---+ + | 2 | 3 | ==> | 6 | + +---+---+ +---+ + +~~~ +: n:mul +c (nn-n) +i mure.... +~~~ + +`n:divmod` divides two values, returning the result and +remainer. + +~~~ +: n:divmod +c (nn-nn) +i dire.... +~~~ + +~~~ +: and +c (nn-n) +i anre.... +~~~ + +~~~ +: or +c (nn-n) +i orre.... +~~~ + +~~~ +: xor +c (nn-n) +i xore.... +~~~ + +~~~ +: shift-left +c (nn-n) +i slre.... +~~~ + +~~~ +: shift-right +c (nn-n) +i srre.... +~~~ + +`io` triggers an i/o operation. The basic ilo system provides +just a few i/o devices. + +Using this is a matter of pushing any need values to the stack, +pushing the i/o device number, then calling `io`. For a list of +devices: + ++------------+--------------------------------------+----------+ +| I/O Device | Action | Required | ++============+======================================+==========+ +| 0 | Display a character. Consumes a | Yes | +| | character from the stack. | | +| 1 | Read a character from the input | Yes | +| | device. The character is pushed to | | +| | the stack. | | +| 2 | Read a block into memory. | Yes | +| 3 | Write memory to a block. | Yes | +| 4 | Write all memory to an image/rom. | No | +| 5 | Reload the image/rom, and jump to | Yes | +| | address 0. Also empty all stacks. | | +| 6 | End execution. On a hosted system, | No | +| | exit ilo. If native, suspend | | +| | execution. | | +| 7 | Obtain stack depths. Pushes the data | Yes | +| | depth then the address depth. | | ++------------+-------------------------------------------------+ + +~~~ +: io +c (...n-?) +i iore.... +~~~ + +`copy` copies "n" cells of memory starting at p1 to p2. For +example: + + +---+---+---+---+---+---+---+---+---+ + | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + +---+---+---+---+---+---+---+---+---+ + | A | B | C | D | D | E | A | B | C | + +---+---+---+---+---+---+---+---+---+ + +With a stack of: + + #2 #6 #3 + +This would change memory to: + + +---+---+---+---+---+---+---+---+---+ + | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + +---+---+---+---+---+---+---+---+---+ + | A | B | C | D | D | B | C | D | C | + +---+---+---+---+---+---+---+---+---+ + +The ilo specification does not require the system to support +overlapping copies. If the memory areas would overlap, test +well and write a specific word to handle the overlap if needed. + +~~~ +: copy +c (ppn-) +i cyre.... +~~~ + +`compare` will compare "n" cells of memory starting at p1 and +p2. + +As an example: + + +---+---+---+---+---+---+---+---+---+ + | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | + +---+---+---+---+---+---+---+---+---+ + | A | B | C | D | D | E | A | B | C | + +---+---+---+---+---+---+---+---+---+ + +With a stack of: + + #1 #7 #3 + +This would return true (-1) as the ABC sequences at 1 & 7 match. +But a stack of: + + #1 #5 #3 + +Would return false (0) since the sequences (ABC & DEA) do not +match. + +~~~ +: compare +c (ppn-) +i cpre.... +~~~ + +================================================================ + +The next set makes a copy of the second item on the stack, puts +a copy of the top item under the second item, discards the +second item, discards the top two items, and makes copies of the +top two items. + + +---+---+ +---+---+---+ + | 1 | 2 | ==> | 1 | 2 | 1 | + +---+---+ +---+---+---+ + +~~~ +: over +c (xy-xyx) +i puduposw +i re...... +~~~ + + +---+---+ +---+---+---+ + | 1 | 2 | ==> | 2 | 1 | 2 | + +---+---+ +---+---+---+ + +~~~ +: tuck +c (xy-yxy) +i dupuswpo +i re...... +~~~ + + +---+---+ +---+ + | 1 | 2 | ==> | 2 | + +---+---+ +---+ + +~~~ +: nip +c (xy-y) +i swdrre.. +~~~ + +`drop-pair` discards the top two stack items. (In some Forths +this is called "2drop") + + +---+---+ + | 1 | 2 | ==> + +---+---+ + +~~~ +: drop-pair +c (xy-) +i drdrre.. +~~~ + +`dup-pair` makes a copy of the top two stack items. (In some +Forths this is called "2dup") + + +---+---+ +---+---+---+---+ + | 1 | 2 | ==> | 1 | 2 | 1 | 2 | + +---+---+ +---+---+---+---+ + +~~~ +: dup-pair +c (xy-xyxy) +i puduposw +i puduposw +i re...... +~~~ + +================================================================ + +There is a second stack, which holds return addresses. It's +used for subroutine calls, but you can temporarily store data +there as well. `push` and `pop` provide access to this stack. + +Note that this is more complicated than simply pushing values. +Thes definitions take the direct threading implementation into +account to ensure that code does not crash when using them, as +long as the `push` and `pop` operations are paired correctly. + +~~~ +: push +c (n-) (A:-n) +i poswposw +i pupupure + +: pop +c (-n) (A:n-) +i popoposw +i puswpure +~~~ + +A better way to work with data items on the address stack is to +use `dip` and `sip`. Using these ensures that the address stack +remains balanced, and is more idomatic. + +In this case, `dip` removes the value before calling the +function, but `sip` does not. In both, the value is restored to +the stack after the function return. + +~~~ +: dip +c (np-n) +i swpuca.. +i pore.... + +: sip +c (np-n) +i puduposw +i puca.... +i pore.... +~~~ + +For those familiar with traditional Forth systems, the `push`, +`pop`, `dip`, and `sip` are similar to: + ++--------------+---------------+ +| Konilo | Traditional | ++==============+===============+ +| push ... pop | >R ... R> | +| [ ... ] dip | >R ... R> | +| [ ... ] sip | dup >R ... R> | ++--------------+---------------+ + +================================================================ + +Konilo provides a number of words for controlling the flow +of execution. + +The initial building blocks are calls, jumps, and returns. A +call takes an address and jumps to it, saving the previous +execution address on the return (or address) stack. Return will +take the address from the return stack and jump back to it. A +jump is like call, but does not save a return address and thus +can not be used with return. + +~~~ +: jump +c (p-) +i popopodr +i drdrju.. + +: call +c (p-) +i ju...... +~~~ + +================================================================ + +`?jump` branches to a function if a passed flag is true +(non-zero). + +~~~ +: ?jump +i swline.. +d 0 +i licj.... +r jump +i drre.... +~~~ + +================================================================ + +# Loops + +The `times` combinator takes a count and an address. It then +runs the function the specified number of times. + +An example: + + #0 #10 [ dup n:put n:inc ] times drop + +~~~ +: times +c (np-) +i sw...... +: times.loop +i dulieq.. +d 0 +i licj.... +r times.done +i lisupudu +d 1 +i puca.... +i popoliju +r times.loop +: times.done +i drdrre.. +~~~ + +The `until` combinator takes a function address. The function +must leave a flag on the stack. `until` runs the function +repeatedly until the flag is true (non-zero). + +~~~ +: until +c (p-) +c pointer must return a value to be used as a flag +i dupuca.. +i poswlieq +d 0 +i licj.... +r until +i drre.... +~~~ + +The `while` combinator takes a function address. The function +must leave a flag on the stack. `while` runs the function +repeatedly until the flag is false (zero). + +~~~ +: while +c (p-) +c pointer must return a value to be used as a flag +i dupuca.. +i poswline +d 0 +i licj.... +r while +i drre.... +~~~ + +`forever` runs a function repeatedly in an unending loop. + +~~~ +: forever +c (p-) +i dupuca.. +i poliju.. +r forever +~~~ + +================================================================ + +`restart` triggers the reset functionality in ilo. This will +reload the image, reset the stack pointers to empty, and then +jump to address 0. + +~~~ +: restart +c (...-) +i liio.... +d 5 +~~~ + +`bye` triggers the shutdown functionality it ilo. On a hosted +system, this will normally return to the system shell. + +~~~ +: bye +c (-) +i liio.... +d 6 +~~~ + +================================================================ + +# Block I/O + +Block loading & saving is a frequent operation for me, so I have +these here to make them a little more efficient. + +~~~ +: block:load +c (np-) +i pulifead +r BaseBlock +i poliiore +d 2 + +: block:save +c (np-) +i pulifead +r BaseBlock +i poliiore +d 3 + +: BaseBlock +d 0 +~~~ + +# Maths + +`n:zero?` and `n:-zero?` compare a value to zero. + +~~~ +: n:zero? +c (n-f) +i lieqre.. +d 0 + +: n:-zero? +c (n-f) +i linere.. +d 0 +~~~ + +`n:divmod` wraps the instruction that divides and returns the +quotient and remainder. These separate out the functionality. + +~~~ +: n:mod +c (n-n) +i didrre.. + +: n:div +c (n-n) +i diswdrre +~~~ + +`n:min` returns the lesser of two values; `n:max` returns the +greater. + +~~~ +: n:min +c (nn-n) +i lica.... +r dup-pair +i ltlilili +r drop +r nip +r choose +i ju...... +~~~ + +~~~ +: n:max +c (nn-n) +i lica.... +r dup-pair +i gtlilili +r drop +r nip +r choose +i ju...... +~~~ + +I use `n:min` and `n:max` to implement `n:limit`, which takes a +value, and an upper and lower limit. It will return the value +if it is in range, or the closest value in range if outside it. + +~~~ +: n:limit +i swlilica +r n:min +r dip +i liju.... +r n:max +~~~ + +~~~ +: not +c (n-n) +i lixore.. +d -1 +~~~ + +================================================================ + +# Memory + +`fetch-next` takes an address. It fetches the value stored at +the address, and returns both this and the next address. + +~~~ +: fetch-next +c (p-pn) +i duliadsw +d 1 +i fere.... +~~~ + +`store-next` takes a value and an address. It stores the value +in the address, then returns the next address. + +~~~ +: store-next +c (np-p) +i duliadpu +d 1 +i stpore.. +~~~ + +`fill` is used to fill a region of memory with a specified +value. + +Takes a value, and address, and a length. + +~~~ +: fill +c (npn-) +i pulica.. +r dup-pair +i stliadpo +d 1 +i lisuduli +d 1 +d 0 +i eqlicj.. +r fill.done +i liju.... +r fill +: fill.done +i drdrdrre +~~~ + +`gc` runs a function, saving and restoring the value of `Free`. +This replaces the pattern of `@Free [ ... ] dip !Free` with +`[ ... ] gc`, a shorter and more readable approach. + +~~~ +: gc +c (p-) +i lifepuca +r Free +i polistre +r Free +~~~ + +# Conditionals + +`choose` takes a flag and two function addresses. If the flag +is true (non-zero), it runs the first function. If false (zero), +it will run the second. + +This works by storing the pointers in a table. A pointer to the +second entry in the table (the false operation) is then placed +on the stack, and the flag is added to the pointer. If true (-1) +it will then point to the true function. I can then just fetch +and branch. + +A small bit of logic is inserted to ensure the flags are either +-1 or 0. + +~~~ +: choice:true +d 0 + +: choice:false +d 0 + +: choose +c (fpp-) +c store possible branch targets +i listlist +r choice:false +r choice:true +c normalize the flag to 0 or -1, then add this to the "false" +c target address +i lineliad +d 0 +r choice:false +c branch to the selected target +i feju.... +~~~ + +`if` and `-if` each take a flag and a function pointer. `if` +will run the function if the flag is true. `-if` will run it if +the flag is false. + +~~~ +: -if +c (fp-) +i pulieqpo +d 0 +: if +c (fp-) +i cj...... +i re...... +~~~ + +`lteq?` checks to see if a value is less than or equal to +another value. Given: + + | 1 | 2 | + +This would return true (-1) since 1 is less than or equal to 2. + +~~~ +: lteq? +c (nn-f) +i lica.... +r dup-pair +i eqpultpo +i orre.... +~~~ + +`gteq?` is like `lteq?`, but checks for greater than or equal +to. + +~~~ +: gteq? +c (nn-f) +i swliju.. +r lteq? +~~~ + +# Dictionary + +The dictionary maps names to functions. It is set up as a simple +linked list, with the following fields: + ++---+-----------+---------------------------+ +| # | Accessor | Description | ++===+===========+===========================+ +| 0 | d:link | link to previous entry | +| 1 | d:hash | hash of word name | +| 2 | d:address | pointer to word start | +| 3 | d:flags | special flags (immediate) | ++---+-----------+---------------------------+ + +Note that the name of the word is not actually saved, just a +hash of it. This saves considerable space and makes lookups +fast, but comes at the cost of some flexibility. + +Prior implementations of Konilo have used *word classes* to +handle different word behaviors. This implementation does not. +You have normal words or immediate ones, with all other cases +being handled by use of sigils. + +Immediate words are kept to a minimum: + + ; [ ] + +`d:lookup` takes a string with the word name and returns the +address of the dictionary header, or zero if the word is not +found in the dictionary. + +~~~ +: d:lookup +c (s-d) + +c get the hash of the nam we are trying to find +i lica.... +r a:hash + +c push a pointer to the most recent entry to the stack, +c then run a loop looking for the end. When done, clean +c up the stack and return a pointer to the dictionary +c header (or 0 if not found) +i lifelica +r Latest +r d:lookup.next +i swdrre.. + +: d:lookup.next +c the lookup loop begins + +c duplicate the top two entries on the stack +i puduposw +i puduposw + +c get the contents of the `d:hash` field; compare +c to the desired hash. If they match, branch to +c "d:lookup.done". +i liadfene +d 1 +i dulieqli +d 0 +r d:lookup.done +i cj...... + +c if not a match, we fetch the next header. If it +c is zero, no other entries remain. Otherwise we +c then repeat the loop. +i drfedudu +i lieqlicj +d 0 +r d:lookup.done +i drliju.. +r d:lookup.next + +c finish up and return to `d:lookup` +: d:lookup.done +i drre.... +~~~ + +# Dictionary Fields + +Given a dictionary pointer, these return the address of each +field. + +~~~ +: d:link +c (d-p) +i re...... + +: d:hash +c (d-p) +i liadre.. +d 1 + +: d:address +c (d-p) +i liadre.. +d 2 + +: d:flags +c (d-p) +c The flags field is currently only used to track +c immediate words. +i liadre.. +d 3 +~~~ + +`d:exists?` checks for the presence of a word. It uses +`d:lookup` to seach for a word, then returns either true (-1) +or false (0). + +~~~ +: d:exists? +c (s-f) +i lica.... +r d:lookup +i linere.. +d 0 +~~~ + +# Arrays & Strings + +Arrays are linear sequences of values. They start with a count +and are immediately followed by the values. + +Strings are arrays where the values are the ASCII character +codes. + +E.g., + + 'hello + +In memory is: + + 5 104 101 108 108 111 + +The array words are placed in the `a:` namespace. They are +mirrored under the `s:` namespace for strings. + +`a:length` takes a pointer to an array and returns the length. + +~~~ +: a:length +c (a-n) +i fere.... +~~~ + +`a:copy` makes a copy of an array. Provide a pointer to the +array and a pointer to the destination. + + :a:copy over a:length n:inc copy ; + +~~~ +: a:copy +c (ap-) +i puduposw +i feliad.. +d 1 +i liju.... +r copy +~~~ + +`a:dup` takes a pointer to an array. It makes a copy of this +and then returns a pointer to the new one. + + :a:dup here [ dup a:length comma &comma a:for-each ] dip ; + +The copy is made at `here`, so using this can generate garbage. + +~~~ +: a:dup +c (a-a) +c get a pointer to `here`, move it out of the way +i lifepu.. +r Free +c get the length, comma it to the new array +i dulica.. +r a:length +i lica.... +r comma +c run `comma` against each value, copying them to the +c new array +i lilica.. +r comma +r a:for-each +c restore the pointer to the new array, and we're done +i pore.... +~~~ + +`a:th` takes a pointer to an array and an index. It then returns +the address that the index corresponds to. + +~~~ +: a:th +c (an-p) +i liadadre +d 1 +~~~ + +`a:fetch` takes a pointer to an array and an index. It then +returns the value stored in the array at the specified index. + +~~~ +: a:fetch +c (an-n) +i liadadfe +d 1 +i re...... +~~~ + +`a:store` takes a value, a pointer to an array, and an index. +It stores the value into the array at the specified index. + +~~~ +: a:store +c (nan-) +i liadadst +d 1 +i re...... +~~~ + +`a:hash` returns a hash (using a modified djb2 model) for the +values in an array. + +This is a very important function as the dictionary is stored +as hashes of the word names. If you alter this, please be aware +that you will need to update the dictionary hashes, and likely +some code in blocks that use hashes as well. + +The hashes are constrained due to the use of 32-bit signed +integers. If using a host system with larger cells, you will +either need to update the dictionary and other places the hashes +are used, or ensure that your system handles overflow and wrap- +around as expected by ilo. + +~~~ +: a:hash +c (a-n) +i liswlili +d 5381 +r a:hash.values +r a:for-each +i ju...... + +: a:hash.values +i swlimuad +d 33 +i re...... +~~~ + +`a:for-each` takes an array and a pointer to a function. It +then pushes each value from the array to the stack and runs +the code indicated by the pointer for each value. + +Assuming an array of and a pointer to `[ n:put nl ]`: + + here #3 comma $98 comma $99 comma $100 comma + [ n:put nl ] a:for-each + +Is the same as doing: + + $98 n:put nl + $99 n:put nl + $100 n:put nl + +~~~ +: a:for-each +c (ap-) + +c get the length and a pointer to the first data element +c in the array +i swlica.. +r fetch-next + +c reorder the stack. We are now: +c pointer-to-data pointer-to-function length +i puswpo.. + +c run the actual loop over the data, then clean up +i lica.... +r a:for-each.value +i drdrdrre + +: a:for-each.value +c (ppn-ppn) +c is there remaining data? +i duline.. +d 0 +i dulieq.. +d 0 +c if not, branch to the exit point +i licj.... +r a:for-each.value.done +c if so, move the length and function pointers out of the +c way and get a pointer to the next data item and the current +c item +i dr...... +i pupulica +r fetch-next +c reorder the stack to: value ptr-function +c then call the function +i swpodupu +i swpuca.. +c restore the various pointers (data, function) and length, +c then decrease the length by 1. +i popopoli +d 1 +i suliju.. +c and repeat until completed +r a:for-each.value + +: a:for-each.value.done +i drre.... +~~~ + +Use `a:prepend` to merge two arrays into a new one. Takes two +pointers, with the items from the second one being placed +before the items in the first. + + :a:prepend + here + &swap dip swap + &swap dip swap + dup-pair a:length swap a:length n:add comma + &comma a:for-each &comma a:for-each ; + +~~~ +: a:prepend +c (aa-a) +i lica.... +r dtc +- here + +- lit +r swap +- dip +- swap + +- lit +r swap +- dip +- swap + +- dup-pair +- a:length +- swap +- a:length +- n:add +- comma + +- lit +r comma +- a:for-each +- lit +r comma +- a:for-each +d 0 +~~~ + +Use `a:append` to merge two arrays into a new one. Takes two +pointers, with the items from the first one being placed before +the items in the second. + +~~~ +: a:append +c (aa-a) +i swliju.. +r a:prepend +~~~ + +To reverse the order of items in an array, use `a:reverse`. It +takes a pointer to an array and returns a new array. + +A quick Forth take on this: + + :a:reverse (a-a) + here [ + dup fetch dup comma allot (length+space) + here n:dec swap [ over store n:dec ] a:for-each drop + ] dip ; + +~~~ +: a:reverse +c (a-a) +i lifepu.. +r Free +i dufedu.. +i lica.... +r comma +i lica.... +r allot +i lifelisu +r Free +d 1 +i swlilica +r a:reverse.values +r a:for-each +i drpo.... +i dulica.. +r s:temp +i swlistre +r Free + +: a:reverse.values +i puduposw +i stlisure +d 1 +~~~ + +`allot` reserves memory. Provide it a number and it'll reserve +that amount of memory. You can reclaim memory by passing in a +negative value. + +Note that memory is allocated at `here`, in a linear fashion. +Expect issues if you reclaim memory after something else also +allocates memory. + + :allot @Free n:add !Free ; + +~~~ +: allot +c (n-) +i lifead.. +r Free +i listre.. +r Free +~~~ + +================================================================ + +# The main entry point + +On startup this tries to find a word named `startup`. If found, +it then runs it. After this is complete, it drops into listen, +which runs the interactive listener (REPL). + +~~~ +: forth +c look for a word named `startup` +i lilica.. +r $startup +r d:lookup + +c if it was found, run it +i dulieq.. +d 0 +i lililica +r drop +r run-startup +r choose + +c after checking for (and running if found) the latest `startup` +c word, we enter the listener loop. + +: listen +c (-) +i lica.... +r s:get/token +i lica.... +r interpret +i liju.... +r listen +~~~ + +~~~ +: run-startup +i liadfeju +d 2 + +: $startup +s startup +~~~ + +get length +start address, dest address, length +copy +get length +decrement +store new length + +~~~ +: remove-sigil +c sigils receive a string with the input token sans the sigil. +c This removes the sigil by copying the rest of the string over +c the original data in the TIB and decrementing the count. +i lililicy +r sys:buffers/input/text+1 +r sys:buffers/input/text +d 127 +i liliju.. +r sys:buffers/input +r v:dec +~~~ + +The `interpret` word takes a token and processes it. + +If the token starts with a defined sigil, the sigil is removed +and the remainder of the token is passed to the sigil function +to deal with. + +If it does not, the dictionary is searched for the word name. + +Assuming it's found, the word type (normal or immediate) and +address are obtained, and the word is either called or compiled +into a definition as needed. + +If the token was not found in the dictionary, a warning is +reported. + +~~~ +: interpret +c (s-) + +c copy token to tib +i lilicyli +r sys:buffers/input +d 129 +r sys:buffers/input + +c check the first character in a string to see if a sigil has +c been defined for it. Returns a flag normalized to true (-1) +c or false (0) +i duliadfe +d 1 +i lica.... +r sigil:get +i line.... +d 0 + +c then dispatch to appropriate handler +i lililica +r with-sigil +r without-sigil +r choose +i re...... +~~~ + +~~~ +: with-sigil +i lifelica +r sys:buffers/input/text +r remove-sigil +i lica.... +r sigil:get +i ju...... +~~~ + +~~~ +: without-sigil +c see if the word is in the dictionary +i lica.... +r d:lookup +i dulieq.. +d 0 +c if not found, branch to not-found +i licj.... +r not-found +c if found, get the address & flags from the word +i duliadfe +d 2 +i swliadfe +d 3 +c then, based on the flags, either run it (immediate) or pass +c the address to handle-word for further processing +i lililiju +r handle-immediate +r handle-word +r choose + +: not-found +i drlilica +r sys:buffers/input +r a:length +i lieqlicj +d 0 +r empty-token +i lilica.. +r $not-found +r s:put +i lilica.. +r sys:buffers/input +r s:put +i lica.... +r nl +: empty-token +i re...... + +: $not-found +s word not found: +c note: the above string ends with a space character. This may +c be hidden by your editor. +~~~ + +~~~ +: handle-word +c (p-) +c if compiling (`Compiler` set to -1), comma the address into +c the current definition. If not, call the word by falling +c through into handle-immediate. +i lifelicj +r Compiler +r comma + +: handle-immediate +c immediate words are always called. +i ju...... +~~~ + +================================================================ + +String evaluation. I am doing this by remapping `c:get` to read +input from a string instead of the keyboard. This is done to +ensure that evaluation and direct entry are treated the same. + +~~~ +: Source +d 0 + +: End +d 0 + +: At +d 0 +~~~ + +If at the end of the input buffer, reset `c:get` to the default, +then return ASCII 32 (space). + +~~~ +: get/last +c (-c) +i lilist.. +d 0 +r c:get +i lililiad +d 0 +r c:get +d 1 +i stlire.. +d 32 +~~~ + +When not at the end, return the current character, and advance +the At pointer. + +~~~ +: get/next +c (-c) +i lifelife +r Source +r At +i lica.... +r a:fetch +i liliju.. +r At +r v:inc +~~~ + +A top level "get" decides which of the prior functions +(get/last and get/next) to use. + +~~~ +: get +c (-c) +i lifelife +r At +r End +i eqlili.. +r get/last +r get/next +i liju.... +r choose +~~~ + +The "process" function takes in a token and runs `interpret` on +it if the token is not empty. + +~~~ +: process +c (s-) +c check for non-empty token + +c (s-sf) +i dulica.. +r a:length +i line.... +d 0 + +c process token if not empty +i lililiju +r interpret +r drop +r choose +~~~ + +~~~ +: s:evaluate +c (s-) + +c patch `c:get` to jump to "get" instead of reading keyboard +i lilist.. +d 1793 +r c:get +i lililiad +r get +r c:get +d 1 +i st...... + +c obtain length of string to evaluate; set Source, End, and +c At +i dulica.. +r a:length +i listlist +r End +r Source +i lilist.. +d 0 +r At + +c begin the actual evaluation loop. Read in a token, then +c "process" it. +: s:evaluate/loop +i lica.... +r s:get/token +i lica.... +r process + +c Check to see if we have reached the End of the string. If +c so, branch to the exit point. Otherwise, repeat the loop. +i lifelife +r At +r End +i eqlicj.. +r s:evaluate/done +i liju.... +r s:evaluate/loop + +c This is the exit point. +: s:evaluate/done +i re...... +~~~ + +================================================================ + +Convert a string to a number with `s:to-n`. + +The process used here is to fetch each character, determine the +value it represents, adding it to a placeholder on the stack. +The placeholder is then multiplied by ten before the next cycle. +Some care is taken to not do the multiplication if it would +cause the value to overflow. A sign value is factored in as +well. The `-` character changes the sign. + +In higher level Forth: + + 'Length var + 'Currently var + + :scale &Length fetch &Currently fetch -eq? + [ #10 n:mul &Currently v:inc ] if ; + + :s:to-n (s-n) + dup a:length &Length store #1 &Currently store + #1 swap #0 swap + [ dup #45 eq? [ drop swap n:negate swap ] + [ #48 n:sub n:add scale ] choose ] a:for-each + n:mul ; + + '2147483647 s:to-n bye + +~~~ +: s:Length +d 0 + +: s:Current +d 0 + +: s:to-n.scale +c this will adjust the current sum to get it ready for adding +c the next value. Basically, just multiplying by the base (10). + +c The scaling is only done if we aren't at the end of the input. +c This is done to avoid an overflow issue with large numbers. +i lifelife +r s:Length +r s:Current +i gtliliju +r s:to-n.adjust +r if + +: s:to-n.adjust +i limulili +d 10 +r s:Current +r v:inc +i ju...... + +: s:to-n +c (s-n) +i dufelist +r s:Length +i lilist.. +d 1 +r s:Current +i liswlisw +d 1 +d 0 +i lilica.. +r s:to-n.digit +r a:for-each +i mure.... + +: s:to-n.digit +c check to see if the character is a "-" (indicating a negative +c value). If so, branch to the negative handler. Otherwise, +c branch to the general digit handler. +i dulieqli +d 45 +r s:to-n.handle-negative +i liliju.. +r s:to-n.process-digit +r choose + +: s:to-n.handle-negative +c for negative values, we multiply the value by -1 and then +c return to process the next character. +i drswlimu +d -1 +i swliliju +r s:Current +r v:inc + + +: s:to-n.process-digit +c processing digits is modestly involved. I start by checking +c for a few characters I want to ignore: . and , + +c ignore "." +i dulieq.. +d 46 +i licj.... +r s:to-n.ignore + +c ignore "," +i dulieq.. +d 44 +i licj.... +r s:to-n.ignore + +c if not an ignored character, process as digit. Convert the +c ASCII value to a simple decimal, add it to the running sum, +c and then setup for the next character. +i lisuad.. +d 48 +i liju.... +r s:to-n.scale + +: s:to-n.ignore +c handles ignored characters +i drliliju +r s:Current +r v:inc +~~~ + +The inverse is `n:to-s`, which converts a number into a string. + +~~~ +: n:to-s +c (n-s) + +c get the current `here` and move it out of the way. +i lifepu.. +r Free + +c assign `Free` to the numeric conversion buffer. +i lilist.. +r sys:buffers/numeric-conversion +r Free + +c store an initial count (0) +i lilica.. +d 0 +r comma + +c make a copy of the number, but ensure the copy is positive +i dulica.. +r n:abs + +c with these done, we are ready for the main loop. This will +c iterate over the number, dividing by the base, converting to +c ASCII, and then adding it to the string representation. We +c are at the end when the remainder is zero. + +: n:to-s.internal +i lica.... +r convert +i lica.... +r record +i duline.. +d 0 +i licj.... +r n:to-s.internal + +c at this point there are no remaining values. Clean up the +c stack and check to see if the original value was negative. +c if so, call "add-negative-sign" +i drlilt.. +d 0 +i licc.... +r add-negative-sign + +c we now have a complete string, but it's in reversed order. +c This is corrected using `a:reverse`. +i lilica.. +r sys:buffers/numeric-conversion +r a:reverse + +c the last step is to reset `Free` to the saved value from the +c start. +i polistre +r Free + +: add-negative-sign +i liliju.. +d 45 +r record + +: convert +c divide the value by the base (hard coded to 10) to get a +c positional value +i lidisw.. +d 10 +c convert the positional value to an ASCII character +i liswliju +r $valid-digits +r a:fetch + +: record +c add the ASCII character for the digit to the string and +c increment the length +i lica.... +r comma +i liliju.. +r sys:buffers/numeric-conversion +r v:inc + +: $valid-digits +s 0123456789 +~~~ + +================================================================ + +`v:inc` increments the value stored in a variable. + +~~~ +: v:inc +c (p-) +i dufeliad +d 1 +i swstre.. +~~~ + +`v:dec` decrements the value stored in a variable. + +~~~ +: v:dec +c (p-) +i dufelisu +d 1 +i swstre.. +~~~ + +`n:abs` returns the absolute value of a number. + +~~~ +: n:abs +c (n-n) +i duliltli +d 0 +r n:negate +i liju.... +r if +~~~ + +Use `n:negate` to invert the sign of a number. + +~~~ +: n:negate +c (n-n) +i limure.. +d -1 +~~~ + +`nl` displays a newline. + +~~~ +: nl +i liliju.. +d 10 +r c:put +~~~ + +`sp` displays a space. + +~~~ +: sp +i liliju.. +d 32 +r c:put +~~~ + +`tab` displays a tab. + +~~~ +: tab +i liliju.. +d 9 +r c:put +~~~ + +# Input + +~~~ +: c:get +c (-c) +c Leave two cells filled with nop's. This allows the definition +c to be temporarily replaced later. (Used by `s:evaluate`) +i ........ +i ........ +i liiore.. +d 1 +~~~ + +`s:get/token` reads a whitespace delimited token into the text +input buffer (tib). + +If you use this and need to keep the token around, pass it to +`s:temp`. + +~~~ +: s:get/token +c (-s) +c clear tib (by seting length to zero) +i lilist.. +d 0 +r sys:buffers/input + +c run an input loop +i lica.... +r s:get.loop +c clean up the stack and push the TIB address before returning +i drlire.. +r sys:buffers/input + +: s:get.loop +c the actual input handling occurs here +i lica.... +r c:get + +c check to see if the character is an end of line/token (ASCII +c 10 [line feed] or 32 [space]) +i dudulieq +d 10 +i swlieqor +d 32 + +c if we are at end of input, branch to the end +i lixoduli +d -1 +d 0 +i eqlicj.. +r s:get.loop.done + +c if not, check to see if the user entered a backspace (ASCII 8 +c or 127). If so, branch to a backspace handler. +i drdulieq +d 8 +i licj.... +r s:get.backspace +i dulieq.. +d 127 +i licj.... +r s:get.backspace + +c if not end of token or backspace, we add it to the TIB using +c a helper function, then repeat the loop. +i lica.... +r add-to-tib +i liju.... +r s:get.loop + +c this is the exit point of the entry loop +: s:get.loop.done +i drre.... + +: s:get.backspace +c this handles backspacing. Note that this does not prevent +c writing to memory before the start of the TIB. +i dr...... +i lifelisu +r sys:buffers/input +d 1 +i listliju +r sys:buffers/input +r s:get.loop + +: add-to-tib +c this adds a character to the TIB and increases the length +c counter. +i lidufead +r sys:buffers/input +i liadstli +d 1 +r sys:buffers/input +i liju.... +r v:inc +~~~ + +# Output + +~~~ +: c:put +c (c-) +c Leave two cells filled with nop's. This allows the definition +c to be temporarily replaced later. +i ........ +i ........ +i liiore.. +d 0 +~~~ + +Display a string. + +~~~ +: s:put +c (s-) +i liliju.. +r c:put +r a:for-each +~~~ + +Display a number + +~~~ +: n:put +i lica.... +r n:to-s +i liju.... +r s:put +~~~ + +# Setup the System + +# Sigils + +~~~ +: sigil:set +c (ac-) +i liadstre +r Sigils + +: sigil:get +c (c-p) +i liadfere +r Sigils +~~~ + +`setup:sigils` populates the Sigils table with the default +implementations. This is called as part of the startup process. + +~~~ +: setup:sigils +c (-) + +c # for numbers +i lililica +r sigil:# +d 35 +r sigil:set + +c : for colon definitions +i lililica +r sigil:: +d 58 +r sigil:set + +c & for pointers +i lililica +r sigil:& +d 38 +r sigil:set + +c ' for strings +i lililica +r sigil:' +d 39 +r sigil:set + +i re...... +~~~ + +# Stubs + +~~~ +: process-data +i lica.... +r compiling? +i liliju.. +r process-data.compile +r if + +: process-data.compile +i lilica.. +r lit +r comma +i liju.... +r comma +~~~ + +New definitions start with a `:` sigil. This sigil creates a new +dictionary header (via `d:create`), then runs `compiler:dtc` to +setup a call to the `dtc` word, and finally turns the `Compiler` +on. + +~~~ +: sigil:: +c (s-) +i lica.... +r dtc +- d:create +- compiler:dtc +- compiler:on +d 0 +~~~ + +Numbers are provided via the `#` sigil. This is done by +converting the token to a number, then calling `process-data` +to compile it into a definition if necessary. + +~~~ +: sigil:# +c (s-n) +i lica.... +r s:to-n +i liju.... +r process-data +~~~ + +Pointers are provided by the `&` sigil. This will lookup an +entry in the dictionary. It fetches the `d:address` field, then +uses `process-data` to compile it into a definition if +necessary. + +~~~ +: sigil:& +c (s-p) +i lica.... +r d:lookup +i liadfe.. +d 2 +i liju.... +r process-data +~~~ + +The `'` sigil processes strings. At compile time, it passes the +string to `s:keep`. At interpret time it passes it to `s:temp` +instead. Prior to this, it passes the string to `s:rewrite` +for preliminary processing (if defined). + +~~~ +: sigil:' +c (s-s) + +c check to see if `s:rewrite` is in the dictionary +i lilica.. +r $s:rewrite +r d:lookup + +c if it is, call it to rewrite the string +i duline.. +d 0 +i lililica +r run.s:rewrite +r drop +r choose + +c check to see if we are compiling or at the listener. Call +c either `s:keep` or `s:temp` based on the compiler status. +i lica.... +r compiling? +i lililiju +r s:keep +r s:temp +r choose + +c a string with the `s:rewrite` name. +: $s:rewrite +s s:rewrite + +: run.s:rewrite +c this is a helper used to run `s:rewrite` if present. +i liadfeju +d 2 +~~~ + +# Compiler + +Compilation is mostly easy. To compile code, we needs to create +a new dictionary header with `d:create`, set a variable that +indicates we wish to compile to true (`Compiler`), lay down a +call to `dtc`, then just let `interpret` and the sigils handle +the actual details of laying down the addresses. + +~~~ +: Compiler +d 0 +~~~ + +I define helper functions to enable and disable the `Compiler`. + +~~~ +: compiler:on +c (-) +i lilistre +d -1 +r Compiler +~~~ + +~~~ +: compiler:off +c (-) +i lilistre +d 0 +r Compiler +~~~ + +And a `compiling?` to return the value of `Compiler`. This one +is just a readability aid. + +~~~ +: compiling? +c (-f) +i lifere.. +r Compiler +~~~ + +The compiler needs to be able to store data in memory. This is +done with `comma` (called "," in most Forths). To implement +this I need a variable to track the next free memory address. +This variable is named `Free`. I also define a word (`here`) to +return the value of `Free`. + +~~~ +: Free +r FREE-SPACE + +: here +c (-p) +i lifere.. +r Free +~~~ + +With these, `comma` is easy. Fetch `Free`, use `store-next` to +write the value, then update `Free` with the next address. + +~~~ +: comma +c (n-) +i lifelica +r Free +r store-next +i listre.. +r Free +~~~ + +Ending a word is done with `;`. This is one of a small number of +immediate words. + +A DTC sequence ends with a NULL (0) value, so this uses `comma` +to write this value, then `compiler:off` to end compilation. + +~~~ +: ; +c (-) +i lilica.. +d 0 +r comma +i liju.... +r compiler:off +~~~ + +`compiler:dtc` lays down a call to the `dtc` word at the start +of a compiled word. + +~~~ +: compiler:dtc +c (-) +c compile a "lica...." bundle +i lilica.. +d 2049 +r comma +c then a reference to `dtc` +i liliju.. +r dtc +r comma +~~~ + + :d:create + here swap @Latest comma + a:hash comma + dup #4 n:add comma + #0 comma + !Latest ; + +`d:create` makes a new dictionary header. + +~~~ +: d:create +c (s-) + +c get a pointer to `here`, and a pointer to the previous +c entry. Store the pointer to the previous entry. +i lifeswli +r Free +r Latest +i felica.. +r comma + +c calculate the hash of the word name, store it +i lica.... +r a:hash +i lica.... +r comma + +c calculate the starting address of the word and store it +i duliad.. +d 4 +i lica.... +r comma + +c store the default flags (0) +i lilica.. +d 0 +r comma + +c finally, patch Latest (`Dictionary`) to point to the start +c of this entry. +i listre.. +r Latest +~~~ + +# Quotations + +Quotations are anonymous code blocks. They are basically words +with no names attached. Konilo uses them for a lot: loops, +conditionals, some stack flow, etc. + +~~~ +: [ +c (-a) +i lica.... +r dtc +c get the current Compiler state +- compiling? +c begin compiling +- compiler:on +c lay down a call to `internal:quote` +- lit +r quote +- comma +c get a pointer to `here` for later use +- here +- tuck +c store a dummy value for the length. This will be patched +c by `]`. +- lit +d 0 +- comma +c use `compiler:dtc` to lay down code tostart a DTC code +c sequence +- compiler:dtc +d 0 + + +: ] +c (a-a) +i lica.... +r dtc +c lay down a 0 to end the quote definition +- lit +d 0 +- comma +c determine the length of the quotation +- here +- over +- n:sub +- n:dec +c update the dummy length with the actual length +- swap +- store +c reset the compiler state to whatever it was prior +c to invoking `[` +- lit +r Compiler +- store +c if the compiler is still on, we are nested in a +c defintion. In this case, we can just drop the value +c on the stack. +c +c if at the interpreter, we can just increment the +c value on the stack to get a pointer to the actual +c code. This gets left on the stack. +- compiling? +- lit +r drop +- lit +r interpreting-quote +- choose +d 0 + +: interpreting-quote +i liju.... +r n:inc +~~~ + +# Direct Threading + +Most words in Konilo are direct threaded. This means that +they consist of a series of addresses, ending in a NULL (0). +A single word, `dtc`, walks through these lists, calling each +address in order. + +The in memory representation of a word like: + + :foo #1 #2 n:add ; + +Would look like: + + 0000 i lica.... + 0001 r dtc + 0002 - internal:lit + 0003 d 1 + 0002 - internal:lit + 0003 d 2 + 0004 - n:add + 0005 d 0 + +The `internal:lit` pushes the value in the following cell to the +data stack and advances the instruction pointer. + +DTC is slower than subroutine threading as it has an additional +layer of processing before each call. But it can be more dense, +and is easily disassembled/decompiled if you have name data. + +~~~ +: dtc +c (-) +c get the starting address of the DTC sequence. This will be +c the cell following the call to `dtc`. +i poliad.. +d 1 +c then walk through the list of addresses +i lica.... +r dtc:walk +c when done, clean up and return to the parent of the caller +i drre.... + +: dtc:walk +c get the function address (and a pointer to the next address) +i lica.... +r fetch-next +c check to see if we are at the end (function pointer set to +c zero). If so, exit the loop. +i dulieqli +d 0 +r dtc:done +i cj...... +c if not, move the pointer to the next function address out of +c the way and call the function +i swpuca.. +c then restore the address of the next function and repeat the +c loop +i poliju.. +r dtc:walk + +: dtc:done +i drre.... +~~~ + +This is exposed to the Forth dictionary as `internal:lit`. (We +had issues with mistyping `lit` and `list`, so moved both this +and the following "quote" under an `internal:` namespace) + +~~~ +: lit +c (-n) +i popolica +r fetch-next +i swpuswpu +i re...... +~~~ + +This is exposed to the Forth dictionary as `internal:quote`. + +~~~ +: quote +c (-p) +i popolica +r fetch-next +i swdupuad +i poswpusw +i pure.... +~~~ + +# Numbers + +`n:inc` and `n:dec` increment and decrement numbers. + +~~~ +: n:inc +c (n-n) +i liadre.. +d 1 + +: n:dec +c (n-n) +i lisure.. +d 1 +~~~ + +# Strings + +In Konilo, strings are arrays containing character data. + +I provide a rotating buffer of space for temporary strings (and +general arrays). In a default configuation this has space for 8 +items. + +It starts with a variable to track the next slot. + +~~~ +: Next-String +d 0 +~~~ + +I use this along with the buffer address (the rather long +`sys:buffers/strings+arrays`) to calculate an address for +a temporary string. + +Note here that strings/arrays are (somewhat) limited to 127 +values. Longer ones can be made and used with `s:keep` or +moved to another buffer. A longer string/array will have some +degree of data corruption with later entries. + +~~~ +: s:temp-pointer +c (-p) +i lifelimu +r Next-String +d 128 +i liadre.. +r sys:buffers/strings+arrays +~~~ + +The next word updates the "Next-String" variable, constraining +it to a max of 8 entries. + +~~~ +: s:next +c (-) +i lica.... +r dtc +c increment Next-String +- lit +r Next-String +- v:inc +c compare Next-String to 8 +- lit +d 8 +- lit +r Next-String +- fetch +- eq? +c if 8, use "s:adjust" to reset the counter +- lit +r s:adjust +- if +d 0 +~~~ + +`s:adjust` resets the "Next-String" variable to zero. It is +called when by "s:next" when it reaches the end of the string +space. + +~~~ +: s:adjust +c (-) +i lilistre +d 0 +r Next-String +~~~ + +`s:temp` moves a string into the temporary buffer pool. + +~~~ +: s:temp +c (s-s) +i lica.... +r dtc +- s:temp-pointer +- tuck +- a:copy +- s:next +d 0 +~~~ + + :data:string pop pop dup fetch-next n:add push swap push ; + +~~~ +: data:string +c (-s) +i popodu.. +i lica.... +r fetch-next +i lica.... +r n:add +i puswpure +~~~ + +`s:keep` inlines a string to memory at `here`. It'll also add +code to skip over the string (in case of compilation being +active). + +~~~ +: s:keep +c (s-s) +i lica.... +r dtc + +c compile a call to `data:string` +- lit +- data:string +- comma + +c get a reference to here, tuck it out of the way +- here +- swap + +c get the string length, store it `here` +- dup +- a:length +- comma + +c then use `a:for-each` to copy the characters to `here` +- lit +r comma +- a:for-each + +c finally, if compiling, drop the pointer from the stack. (If +c not compiling, we just leave it on the stack) +- compiling? +- lit +r drop +- if +d 0 +~~~ + +`s:append` is similar to `a:append`, but specifically uses the +temporary buffer memory. + +~~~ +: s:append +c (ss-s) +i lica.... +r dtc +- here +- push +- s:temp-pointer +- lit +r Free +- store +- s:next +- a:append +- pop +- lit +r Free +- store +d 0 + +: s:prepend +c (ss-) +i swliju.. +r s:append +~~~ +================================================================ +EOF +~~~ +: ENTRY.0 +d 0 +d 2088204551 +r neq? +d 0 +: ENTRY.1 +r ENTRY.0 +d 193429569 +r -if +d 0 +: ENTRY.2 +r ENTRY.1 +d 177632 +r ; +d -1 +: ENTRY.3 +r ENTRY.2 +d 212805696 +r ?jump +d 0 +: ENTRY.4 +r ENTRY.3 +d 67966955 +r BaseBlock +d 0 +: ENTRY.5 +r ENTRY.4 +d -1210660288 +r Compiler +d 0 +: ENTRY.6 +r ENTRY.5 +d 1264838491 +r Latest +d 0 +: ENTRY.7 +r ENTRY.6 +d 2089116775 +r Free +d 0 +: ENTRY.8 +r ENTRY.7 +d -786332176 +r Sigils +d 0 +: ENTRY.9 +r ENTRY.8 +d 177664 +r [ +d -1 +: ENTRY.10 +r ENTRY.9 +d 177666 +r ] +d -1 +: ENTRY.11 +r ENTRY.10 +d 1539635992 +r a:append +d 0 +: ENTRY.12 +r ENTRY.11 +d -294312037 +r a:copy +d 0 +: ENTRY.13 +r ENTRY.12 +d 251383785 +r a:dup +d 0 +: ENTRY.14 +r ENTRY.13 +d -1119160502 +r a:fetch +d 0 +: ENTRY.15 +r ENTRY.14 +d -1309732155 +r a:for-each +d 0 +: ENTRY.16 +r ENTRY.15 +d -294147516 +r a:hash +d 0 +: ENTRY.17 +r ENTRY.16 +d 1957010690 +r a:length +d 0 +: ENTRY.18 +r ENTRY.17 +d 1526142126 +r a:prepend +d 0 +: ENTRY.19 +r ENTRY.18 +d -674869668 +r a:reverse +d 0 +: ENTRY.20 +r ENTRY.19 +d -1103209427 +r a:store +d 0 +: ENTRY.21 +r ENTRY.20 +d -293712106 +r s:temp +d 0 +: ENTRY.22 +r ENTRY.21 +d 2090026588 +r a:th +d 0 +: ENTRY.23 +r ENTRY.22 +d 253189153 +r allot +d 0 +: ENTRY.24 +r ENTRY.23 +d 193486360 +r and +d 0 +: ENTRY.25 +r ENTRY.24 +d 427330826 +r block:load +d 0 +: ENTRY.26 +r ENTRY.25 +d 427567833 +r block:save +d 0 +: ENTRY.27 +r ENTRY.26 +d 193487813 +r bye +d 0 +: ENTRY.28 +r ENTRY.27 +d 253758370 +r c:get +d 0 +: ENTRY.29 +r ENTRY.28 +d 253768699 +r c:put +d 0 +: ENTRY.30 +r ENTRY.29 +d 2090140673 +r call +d 0 +: ENTRY.31 +r ENTRY.30 +d -161057562 +r choose +d 0 +: ENTRY.32 +r ENTRY.31 +d 255669810 +r comma +d 0 +: ENTRY.33 +r ENTRY.32 +d -748339476 +r compare +d 0 +: ENTRY.34 +r ENTRY.33 +d -1979274138 +r compiling? +d 0 +: ENTRY.35 +r ENTRY.34 +d 2090156064 +r copy +d 0 +: ENTRY.36 +r ENTRY.35 +d 352952457 +r d:address +d 0 +: ENTRY.37 +r ENTRY.36 +d 626189207 +r d:create +d 0 +: ENTRY.38 +r ENTRY.37 +d 2012546722 +r d:exists? +d 0 +: ENTRY.39 +r ENTRY.38 +d -1539492880 +r d:flags +d 0 +: ENTRY.40 +r ENTRY.39 +d -176741337 +r d:hash +d 0 +: ENTRY.41 +r ENTRY.40 +d -176589039 +r d:link +d 0 +: ENTRY.42 +r ENTRY.41 +d 975220285 +r d:lookup +d 0 +: ENTRY.43 +r ENTRY.42 +d 193489474 +r dip +d 0 +: ENTRY.44 +r ENTRY.43 +d 2090195226 +r drop +d 0 +: ENTRY.45 +r ENTRY.44 +d 288947475 +r drop-pair +d 0 +: ENTRY.46 +r ENTRY.45 +d 193489824 +r dtc +d 0 +: ENTRY.47 +r ENTRY.46 +d 193489870 +r dup +d 0 +: ENTRY.48 +r ENTRY.47 +d -59285433 +r dup-pair +d 0 +: ENTRY.49 +r ENTRY.48 +d 193490778 +r eq? +d 0 +: ENTRY.50 +r ENTRY.49 +d 258875503 +r fetch +d 0 +: ENTRY.51 +r ENTRY.50 +d -1885660229 +r fetch-next +d 0 +: ENTRY.52 +r ENTRY.51 +d 2090257196 +r fill +d 0 +: ENTRY.53 +r ENTRY.52 +d -1163346114 +r forever +d 0 +: ENTRY.54 +r ENTRY.53 +d 5863407 +r gc +d 0 +: ENTRY.55 +r ENTRY.54 +d 193493055 +r gt? +d 0 +: ENTRY.56 +r ENTRY.55 +d 260584565 +r gteq? +d 0 +: ENTRY.57 +r ENTRY.56 +d 2090324905 +r here +d 0 +: ENTRY.58 +r ENTRY.57 +d 5863476 +r if +d 0 +: ENTRY.59 +r ENTRY.58 +d 314257922 +r interpret +d 0 +: ENTRY.60 +r ENTRY.59 +d 5863485 +r io +d 0 +: ENTRY.61 +r ENTRY.60 +d 2090414049 +r jump +d 0 +: ENTRY.62 +r ENTRY.61 +d -1223977595 +r lit +d 0 +: ENTRY.63 +r ENTRY.62 +d -1465379862 +r quote +d 0 +: ENTRY.64 +r ENTRY.63 +d 193498500 +r lt? +d 0 +: ENTRY.65 +r ENTRY.64 +d 266514170 +r lteq? +d 0 +: ENTRY.66 +r ENTRY.65 +d 266796867 +r n:abs +d 0 +: ENTRY.67 +r ENTRY.66 +d 266796918 +r n:add +d 0 +: ENTRY.68 +r ENTRY.67 +d 266800217 +r n:dec +d 0 +: ENTRY.69 +r ENTRY.68 +d 266800368 +r n:div +d 0 +: ENTRY.70 +r ENTRY.69 +d 1637942608 +r n:divmod +d 0 +: ENTRY.71 +r ENTRY.70 +d 266805959 +r n:inc +d 0 +: ENTRY.72 +r ENTRY.71 +d -1502694228 +r n:limit +d 0 +: ENTRY.73 +r ENTRY.72 +d 266809907 +r n:max +d 0 +: ENTRY.74 +r ENTRY.73 +d 266810161 +r n:min +d 0 +: ENTRY.75 +r ENTRY.74 +d 266810349 +r n:mod +d 0 +: ENTRY.76 +r ENTRY.75 +d 266810555 +r n:mul +d 0 +: ENTRY.77 +r ENTRY.76 +d 2024000897 +r n:negate +d 0 +: ENTRY.78 +r ENTRY.77 +d 266813830 +r n:put +d 0 +: ENTRY.79 +r ENTRY.78 +d 266817079 +r n:sub +d 0 +: ENTRY.80 +r ENTRY.79 +d 215056784 +r n:to-s +d 0 +: ENTRY.81 +r ENTRY.80 +d -1486229492 +r n:zero? +d 0 +: ENTRY.82 +r ENTRY.81 +d -494948871 +r n:-zero? +d 0 +: ENTRY.83 +r ENTRY.82 +d 193500364 +r nip +d 0 +: ENTRY.84 +r ENTRY.83 +d 5863647 +r nl +d 0 +: ENTRY.85 +r ENTRY.84 +d 193500566 +r not +d 0 +: ENTRY.86 +r ENTRY.85 +d 5863686 +r or +d 0 +: ENTRY.87 +r ENTRY.86 +d 2090594561 +r over +d 0 +: ENTRY.88 +r ENTRY.87 +d 193502740 +r pop +d 0 +: ENTRY.89 +r ENTRY.88 +d -853324053 +r process-data +d 0 +: ENTRY.90 +r ENTRY.89 +d 2090629861 +r push +d 0 +: ENTRY.91 +r ENTRY.90 +d 1059716234 +r restart +d 0 +: ENTRY.92 +r ENTRY.91 +d -127536406 +r s:append +d 0 +: ENTRY.93 +r ENTRY.92 +d 410125037 +r a:copy +d 0 +: ENTRY.94 +r ENTRY.93 +d 272730363 +r a:dup +d 0 +: ENTRY.95 +r ENTRY.94 +d 102250697 +r s:evaluate +d 0 +: ENTRY.96 +r ENTRY.95 +d 652426460 +r a:fetch +d 0 +: ENTRY.97 +r ENTRY.96 +d -89307369 +r a:for-each +d 0 +: ENTRY.98 +r ENTRY.97 +d 704009186 +r s:get/token +d 0 +: ENTRY.99 +r ENTRY.98 +d 410289558 +r a:hash +d 0 +: ENTRY.100 +r ENTRY.99 +d 410401271 +r s:keep +d 0 +: ENTRY.101 +r ENTRY.100 +d 289838292 +r a:length +d 0 +: ENTRY.102 +r ENTRY.101 +d -1950939456 +r s:prepend +d 0 +: ENTRY.103 +r ENTRY.102 +d 272743435 +r s:put +d 0 +: ENTRY.104 +r ENTRY.103 +d 143016046 +r a:reverse +d 0 +: ENTRY.105 +r ENTRY.104 +d 668377535 +r a:store +d 0 +: ENTRY.106 +r ENTRY.105 +d 410724968 +r s:temp +d 0 +: ENTRY.107 +r ENTRY.106 +d 2090673454 +r a:th +d 0 +: ENTRY.108 +r ENTRY.107 +d 410733744 +r s:to-n +d 0 +: ENTRY.109 +r ENTRY.108 +d -38720901 +r shift-left +d 0 +: ENTRY.110 +r ENTRY.109 +d -1270529650 +r shift-right +d 0 +: ENTRY.111 +r ENTRY.110 +d -1801857825 +r drop +d 0 +: ENTRY.112 +r ENTRY.111 +d -1801857830 +r sigil:# +d 0 +: ENTRY.113 +r ENTRY.112 +d -1801857827 +r sigil:& +d 0 +: ENTRY.114 +r ENTRY.113 +d -1801857826 +r sigil:' +d 0 +: ENTRY.115 +r ENTRY.114 +d -1801857807 +r sigil:: +d 0 +: ENTRY.116 +r ENTRY.115 +d 576954903 +r sigil:get +d 0 +: ENTRY.117 +r ENTRY.116 +d 576967971 +r sigil:set +d 0 +: ENTRY.118 +r ENTRY.117 +d 193505809 +r sip +d 0 +: ENTRY.119 +r ENTRY.118 +d 5863816 +r sp +d 0 +: ENTRY.120 +r ENTRY.119 +d 274826578 +r store +d 0 +: ENTRY.121 +r ENTRY.120 +d 1976567422 +r store-next +d 0 +: ENTRY.122 +r ENTRY.121 +d 2090739264 +r swap +d 0 +: ENTRY.123 +r ENTRY.122 +d -1371592987 +r sys:buffers/block +d 0 +: ENTRY.124 +r ENTRY.123 +d -1359625529 +r sys:buffers/loops +d 0 +: ENTRY.125 +r ENTRY.124 +d 942827360 +r sys:buffers/numeric-conversion +d 0 +: ENTRY.126 +r ENTRY.125 +d -1351755340 +r sys:buffers/scope +d 0 +: ENTRY.127 +r ENTRY.126 +d -1357624343 +r sys:buffers/needs +d 0 +: ENTRY.128 +r ENTRY.127 +d 1106725658 +r sys:buffers/reserved +d 0 +: ENTRY.129 +r ENTRY.128 +d 1438625409 +r sys:buffers/strings+arrays +d 0 +: ENTRY.130 +r ENTRY.129 +d -1363217974 +r sys:buffers/input +d 0 +: ENTRY.131 +r ENTRY.130 +d 193506620 +r tab +d 0 +: ENTRY.132 +r ENTRY.131 +d 275614599 +r times +d 0 +: ENTRY.133 +r ENTRY.132 +d 2090773084 +r tuck +d 0 +: ENTRY.134 +r ENTRY.133 +d 276987953 +r until +d 0 +: ENTRY.135 +r ENTRY.134 +d 276287585 +r v:dec +d 0 +: ENTRY.136 +r ENTRY.135 +d 276293327 +r v:inc +d 0 +: ENTRY.137 +r ENTRY.136 +d 279132286 +r while +d 0 +: ENTRY.138 +r ENTRY.137 +d 193511454 +r xor +d 0 + +: Latest +r ENTRY.138 +: FREE-SPACE +~~~ +