w================================================================ ,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 - 63376 | reserved for future system use | | 63377 - 64377 | misc. system variables | | 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 61026 : 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 c ! for fetching from variables o 61240 r sigil:! c # for numbers o 61242 r sigil:# c $ for characters o 61243 r sigil:$ c & for pointers o 61245 r sigil:& c ' for strings o 61246 r sigil:' c ( for comments o 61247 r drop c : for colon definitions o 61265 r sigil:: c @ for fetching from variables o 61271 r sigil:@ c \ for creating names o 61299 r sigil:\ o 61336 : sys:buffers/reserved o 63377 : sys:buffers/variables o 64378 : sys:buffers/strings+arrays o 65407 : sys:buffers/input o 65408 : sys:buffers/input/text o 65409 : sys:buffers/input/text+1 ~~~ ~~~ c A number of parts of the system make use of variables. I store c these in the system buffers o 63377 c Compiler tracks the compiler state (-1 for on, 0 for off) : Compiler d 0 : BaseBlock c BaseBlock is used by the block i/o words d 0 : Blocks c number of blocks available d 16 : Block c number of the current block d 0 c These are used to construct the choice table used by `choose` : choice:true d 0 : choice:false d 0 c ACount stores the number of matches found by `a:indices` : ACount d 0 c Current is used by `indexed-times` to track the current loop c depth : Current d 0 c Source, End, and At are used by the string evaluation code. : Source d 0 : End d 0 : At d 0 c s:Length & s:Current are used by `s:to-n` : s:Length d 0 : s:Current d 0 c Next-String holds the number for the next string in the temp. c pool : Next-String d 0 c Used by `needs` : needs.Len d 0 ~~~ ~~~ c After creating the labels for the system buffers, return to c address 0 to start the actual code. c On startup this calls a function to populate the default c sigils, then jumps into forth. o 0 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. ~~~ c `dup` duplicates the top item on the data stack. c c +---+ +---+---+ c | 1 | ==> | 1 | 1 | c +---+ +---+---+ c : dup c (n-nn) i dure.... c `drop` discards the top item on the data stack. c c +---+---+ +---+ c | 1 | 2 | ==> | 1 | c +---+---+ +---+ c : drop c (n-) i drre.... c `swap` exchanges the positions of the top two values on the c data stack. c c +---+---+ +---+---+ c | 1 | 2 | ==> | 2 | 1 | c +---+---+ +---+---+ c : swap c (nm-mn) i swre.... c `eq?` compares two items on the stack for equality. If they c are equal, it will push a true (-1) flag. If they are not c equal, it pushes false (0) instead. c c +---+---+ +----+ c | 1 | 1 | ==> | -1 | c +---+---+ +----+ c c +---+---+ +----+ c | 2 | 1 | ==> | 0 | c +---+---+ +----+ c : eq? c (nn-f) i eqre.... c `-eq?` compares two items on the stack for inequality. If they c are not equal, it will push a true (-1) flag. If they are c equal, it pushes false (0) instead. c c +---+---+ +----+ c | 1 | 1 | ==> | 0 | c +---+---+ +----+ c c +---+---+ +----+ c | 2 | 1 | ==> | -1 | c +---+---+ +----+ c : neq? c (nn-f) i nere.... c +---+---+ +----+ c | 1 | 1 | ==> | 0 | c +---+---+ +----+ c c +---+---+ +----+ c | 2 | 1 | ==> | 0 | c +---+---+ +----+ c c +---+---+ +----+ c | 1 | 2 | ==> | -1 | c +---+---+ +----+ c : lt? c (nn-f) i ltre.... c +---+---+ +----+ c | 1 | 1 | ==> | 0 | c +---+---+ +----+ c c +---+---+ +----+ c | 2 | 1 | ==> | -1 | c +---+---+ +----+ c c +---+---+ +----+ c | 1 | 2 | ==> | 0 | c +---+---+ +----+ c : gt? c (nn-f) i gtre.... : fetch c (p-n) i fere.... : store c (np-) i stre.... c `n:add` adds two values, returning the result. c c +---+---+ +---+ c | 1 | 2 | ==> | 3 | c +---+---+ +---+ c : n:add c (nn-n) i adre.... c `n:sub` subtracts two values, returning the result. c c +---+---+ +---+ c | 3 | 1 | ==> | 2 | c +---+---+ +---+ c : n:sub c (nn-n) i sure.... c `n:mul` multiplies two values, returning the result. c c +---+---+ +---+ c | 2 | 3 | ==> | 6 | c +---+---+ +---+ c : n:mul c (nn-n) i mure.... c `n:divmod` divides two values, returning the result and c remainer. c : 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.... c `io` triggers an i/o operation. The basic ilo system provides c just a few i/o devices. c c Using this is a matter of pushing any needed values to the c stack, pushing the i/o device number, then calling `io`. For c a list of devices: c c +--------+--------------------------------------+----------+ c | Device | Action | Required | c +========+======================================+==========+ c | 0 | Display a character. Consumes a | Yes | c | | character from the stack. | | c | 1 | Read a character from the input | Yes | c | | device. The character is pushed to | | c | | the stack. | | c | 2 | Read a block into memory. | Yes | c | 3 | Write memory to a block. | Yes | c | 4 | Write all memory to an image/rom. | No | c | 5 | Reload the image/rom, and jump to | Yes | c | | address 0. Also empty all stacks. | | c | 6 | End execution. On a hosted system, | No | c | | exit ilo. If native, suspend | | c | | execution. | | c | 7 | Obtain stack depths. Pushes the data | Yes | c | | depth then the address depth. | | c +--------+-------------------------------------------------+ c : io c (...n-?) i iore.... c `copy` copies "n" cells of memory starting at p1 to p2. For c example: c c +---+---+---+---+---+---+---+---+---+ c | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | c +---+---+---+---+---+---+---+---+---+ c | A | B | C | D | D | E | A | B | C | c +---+---+---+---+---+---+---+---+---+ c c With a stack of: c c #2 #6 #3 c c This would change memory to: c c +---+---+---+---+---+---+---+---+---+ c | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | c +---+---+---+---+---+---+---+---+---+ c | A | B | C | D | D | B | C | D | C | c +---+---+---+---+---+---+---+---+---+ c c The ilo specification does not require the system to support c overlapping copies. If the memory areas would overlap, test c and write a specific word to handle the overlap if needed. c : copy c (ppn-) i cyre.... c `compare` will compare "n" cells of memory starting at p1 and c p2. c c As an example: c c +---+---+---+---+---+---+---+---+---+ c | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | c +---+---+---+---+---+---+---+---+---+ c | A | B | C | D | D | E | A | B | C | c +---+---+---+---+---+---+---+---+---+ c c With a stack of: c c #1 #7 #3 c c This would return true (-1) as the ABC sequences at 1 & 7 c match. c c But a stack of: c c #1 #5 #3 c c Would return false (0) since the sequences (ABC & DEA) do not c match. c : compare c (ppn-) i cpre.... ~~~ ================================================================ ~~~ c The next set makes a copy of the second item on the stack, c puts a copy of the top item under the second item, discards c the second item, discards the top two items, and makes copies c of the top two items. c c +---+---+ +---+---+---+ c | 1 | 2 | ==> | 1 | 2 | 1 | c +---+---+ +---+---+---+ c : over c (xy-xyx) i puduposw i re...... c +---+---+ +---+---+---+ c | 1 | 2 | ==> | 2 | 1 | 2 | c +---+---+ +---+---+---+ c : tuck c (xy-yxy) i dupuswpo i re...... c +---+---+ +---+ c | 1 | 2 | ==> | 2 | c +---+---+ +---+ c : nip c (xy-y) i swdrre.. c `drop-pair` discards the top two stack items. (In some Forths c this is called "2drop") c c +---+---+ c | 1 | 2 | ==> c +---+---+ c : drop-pair c (xy-) i drdrre.. c `dup-pair` makes a copy of the top two stack items. (In some c Forths this is called "2dup") c c +---+---+ +---+---+---+---+ c | 1 | 2 | ==> | 1 | 2 | 1 | 2 | c +---+---+ +---+---+---+---+ c : dup-pair c (xy-xyxy) i puduposw i puduposw i re...... ~~~ ================================================================ ~~~ c There is a second stack, which holds return addresses. It's c used for subroutine calls, but you can temporarily store data c there as well. `push` and `pop` provide access to this stack. c c Note that this is more complicated than simply pushing values. c Thess definitions take the direct threading implementation c into account to ensure that code does not crash when using c them, as long as the `push` and `pop` operations are paired c correctly. c : push c (n-) (A:-n) i poswposw i pupupure : pop c (-n) (A:n-) i popoposw i puswpure c A better way to work with data items on the address stack is c to use `dip` and `sip`. Using these ensures that the address c stack remains balanced, and is more idomatic. c c In this case, `dip` removes the value before calling the c function, but `sip` does not. In both, the value is restored c to the stack after the function return. c : dip c (np-n) i swpuca.. i pore.... : sip c (np-n) i puduposw i puca.... i pore.... c For those familiar with traditional Forth systems, the `push`, c `pop`, `dip`, and `sip` are similar to: c c +--------------+---------------+ c | Konilo | Traditional | c +==============+===============+ c | push ... pop | >R ... R> | c | [ ... ] dip | >R ... R> | c | [ ... ] sip | dup >R ... R> | c +--------------+---------------+ c ~~~ ================================================================ ~~~ c Konilo provides a number of words for controlling the flow c of execution. c c The initial building blocks are calls, jumps, and returns. A c call takes an address and jumps to it, saving the previous c execution address on the return (or address) stack. Return c will take the address from the return stack and jump back to c it. A jump is like call, but does not save a return address c and thus can not be used with return. c : jump c (p-) i popopodr i drdrju.. : call c (p-) i ju...... ~~~ ================================================================ ~~~ c `?jump` branches to a function if a passed flag is true c (non-zero). : ?jump i swline.. d 0 i licj.... r jump i drre.... ~~~ ================================================================ # Loops ~~~ c The `times` combinator takes a count and an address. It then c runs the function the specified number of times. c c An example: c c #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.. c The `until` combinator takes a function address. The function c must leave a flag on the stack. `until` runs the function c 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.... c The `while` combinator takes a function address. The function c must leave a flag on the stack. `while` runs the function c 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.... c `forever` runs a function repeatedly in an unending loop. : forever c (p-) i dupuca.. i poliju.. r forever ~~~ ================================================================ ~~~ c `restart` triggers the reset functionality in ilo. This will c reload the image, reset the stack pointers to empty, and then c jump to address 0. : restart c (...-) i liio.... d 5 c `bye` triggers the shutdown functionality it ilo. On a hosted c system, this will normally return to the system shell. : bye c (-) i liio.... d 6 ~~~ ================================================================ # Block I/O ~~~ : block:load c (np-) i pulifead r BaseBlock i poliiore d 2 : block:save c (np-) i pulifead r BaseBlock i poliiore d 3 ~~~ # Maths ~~~ c `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 c `n:divmod` wraps the instruction that divides and returns the c quotient and remainder. These separate out the functionality. : n:mod c (n-n) i didrre.. : n:div c (n-n) i diswdrre c `n:min` returns the lesser of two values; `n:max` returns the c greater. : n:min c (nn-n) c inline dup-pair i puduposw i puduposw i ltlilili r drop r nip r choose i ju...... : n:max c (nn-n) c inline dup-pair i puduposw i puduposw i gtlilili r drop r nip r choose i ju...... c I use `n:min` and `n:max` to implement `n:limit`, which takes c a value, and an upper and lower limit. It will return the c value if it is in range, or the closest value in range if c 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 ~~~ c `fetch-next` takes an address. It fetches the value stored at c the address, and returns both this and the next address. : fetch-next c (p-pn) i duliadsw d 1 i fere.... c `store-next` takes a value and an address. It stores the value c in the address, then returns the next address. : store-next c (np-p) i duliadpu d 1 i stpore.. c `fill` is used to fill a region of memory with a specified c value. c c Takes a value, and address, and a length. : fill c (npn-) i pu...... c inline dup-pair i puduposw i puduposw i stliadpo d 1 i lisuduli d 1 d 0 i eqlicj.. r fill.done i liju.... r fill : fill.done i drdrdrre c `gc` runs a function, saving and restoring the value of `Free` c This replaces the pattern of `@Free [ ... ] dip !Free` with c `[ ... ] gc`, a shorter and more readable approach. : gc c (p-) i lifepuca r Free i polistre r Free ~~~ # Conditionals ~~~ c `choose` takes a flag and two function addresses. If the flag c is true (non-zero), it runs the first function. If false c (zero), it will run the second. c c This works by storing the pointers in a table. A pointer to c the second entry in the table (the false operation) is then c placed on the stack, and the flag is added to the pointer. If c true (-1) it will then point to the true function. I can then c just fetch and branch. c c A small bit of logic is inserted to ensure the flags are c either -1 or 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.... c `if` and `-if` each take a flag and a function pointer. `if` c will run the function if the flag is true. `-if` will run it c 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) c inline dup-pair i puduposw i puduposw 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 | +---+-----------+---------------------------+ 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: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 feliadcy d 1 i re...... ~~~ `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 lifepusw r Free i poswpusw i poswlica r dup-pair i lica.... r a:length i swlica.. r a:length i adlica.. r comma i lilica.. r comma r a:for-each i liliju.. r comma r a:for-each ~~~ 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 ~~~ 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 c (...n-a) i lifepu.. r Free i dulica.. r comma i lilica.. r comma r times i pore.... : a:make/temp c (...n-a) i liliju.. r a:make/temp.internal r gc : a:make/temp.internal i lica.... r a:make i liju.... r s:temp ~~~ `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 c (anq-n) i puswpoli r a:for-each i ju...... ~~~ 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 c (aq-a) i swdupu.. i lica.... r fetch-next i lilica.. r a:map.inner r times i drdrpore : a:map.inner i dupufepu c "over" i duposwca i polilica r store r sip i liadre.. d 1 ~~~ 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 c (afl-a) c here push dup comma push n:inc n:add pop here fetch swap copy c pop dup !Free s:temp i lifepu.. r Free i dulica.. r comma i puliadad d 1 i polifesw r Free i cypoduli r Free i stliju.. r s:temp : a:left c (an-a) c #0 swap a:middle i liswliju d 0 r a:middle : a:right c (an-a) c over fetch over n:sub swap a:middle i puduposw i fe...... i puduposw i suswliju r a:middle ~~~ `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 ~~~ 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 c (s-) i liliju.. r s:rewrite.spaces r a:map : s:rewrite.spaces i dulieqli d 95 r s:rewrite.replace i liju.. r if : s:rewrite.replace i drlire.. d 32 ~~~ `a:indices` returns an array with the locations of a value that matches the passed value. `a:index` returns the first location that matches. ~~~ : a:indices c (av-a) c reset the count i lilist.. d 0 r ACount i lifepu.. r Free i swlilica d 0 r comma i lilica.. r a:indices.iterate r a:for-each i drpolife r Free i puduposw i sulisu.. d 1 i puduposw i stdulica r s:temp i swlistre.. r Free : a:indices.iterate i puduposw i eqlilica r a:indices.record r if i liliju.. r ACount r v:inc : a:indices.record i lifeliju r ACount r comma : a:index c (av-n) i lifepu.. r Free i lica.... r a:indices i lilica.. d 0 r a:fetch i polistre r Free ~~~ ~~~ : a:contains? c (an-f) i swliswli d 0 r a:contains?.inner i lica.... r a:for-each i swdrre.. : a:contains?.inner i swpu.... i puduposw i eqpoorre ~~~ `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 i lifepuli r Free r a:filter.action i lica.... r curry i lifepu.. r Free i puduposw i felica.. r comma i lica.... r a:for-each i polifepu r Free i duposwsu i lisupudu d 1 i poswst.. i lica.... r s:temp i polistre r Free : a:filter.action i puduposw i puca.... i poswlili r comma r drop i liju.... r choose ~~~ ~~~ : c:lowercase? c (c-f) $a $z n:between? ; i lililiju d 97 d 122 r n:between? : c:uppercase? c (c-f) $A $Z n:between? ; i lililiju d 65 d 90 r n:between? : c:to-upper c (c-c) dup c:lowercase? [ #32 n:sub ] if ; i dulica.. r c:lowercase? i liliju.. r c:to-upper.change r if : c:to-upper.change i lisure.. d 32 : c:to-lower c (c-c) dup c:uppercase? [ #32 n:add ] if ; i dulica.. r c:uppercase? i liliju.. r c:to-lower.change r if : c:to-lower.change i liadre.. d 32 : c:to-s c (c-s) '_ s:temp tuck #0 s:store ; i lilica.. r c:to-s.temp r s:temp i luca.... r tuck i liliju.. d 0 r a:store : c:to-s.temp s _ : s:to-upper c (s-s) [ s:dup &c:to-upper s:map s:temp ] gc i lifepu.. r Free i lica.... r a:dup i lilica.. r c:to-upper r a:map i lica.... r s:temp i polistre r Free : s:to-lower c (s-s) [ s:dup &c:to-lower s:map s:temp ] gc ; i lifepu.. r Free i lica.... r a:dup i lilica.. r c:to-lower r a:map i lica.... r s:temp i polistre r Free ~~~ ~~~ : bi c (xqq-) &sip dip call ; i pulica.. r sip i poju.... : bi@ c (xyq-) dup bi* ; c using fall-through here i du...... : bi* c (xyqq-) &dip dip call ; i pulica.. r dip i poju.... : tri c (xqqq-) [ &sip dip sip ] dip call ; i pulilica r sip r dip i lica.... r sip i poju.... : tri@ c (xyzq-) dup dup tri* ; c using fall-through here i dudu.... : tri* c (xyzqqq-) [ [ swap &dip dip ] dip dip ] dip call ; i pupusw.. i lilica.. r dip r dip i polica.. r dip i poju.... : a:eq? c (aa-f) &a:hash bi@ eq? ; i lilica.. r a:hash r bi@ i eqre.... : a:-eq? c (aa-a) &a:hash bi@ -eq? ; i lilica.. r a:hash r bi@ i nere.... : a:chop c (a-a) a:temp &v:dec sip ; i lica.... r s:temp i liliju.. r v:dec r sip : a:behead c (a-a) c a:chop [ [ n:inc dup n:inc swap ] c &a:length bi copy ] sip ; i lica.... r a:chop i dupulili r a:behead.inner r a:length i lica.... r bi i cypore.. : a:behead.inner i liadduli d 1 d 1 i adswre.. : a:first c (a-n) #0 a:fetch ; i liliju.. d 0 r a:fetch : a:last c (a-n) dup a:length n:dec a:fetch ; i dulica.. r a:length i lisuliju d 1 r a:fetch ~~~ ~~~ : get-index i lilifead r sys:buffers/loops r Current i re...... : prepare c increment Current i lilica.. r Current r v:inc c zero out the loop counter i lilica.. d 0 r get-index i stre.... : cleanup c decrement Current i liliju.. r Current r v:dec : indexed-times.inner i swpudupu i ca...... i polica.. r get-index i lica.... r v:inc i polisu.. d 1 i lica.... r tuck i linelicj d 0 r indexed-times.inner i drdrre.. : I c (-n) i lica.... r get-index i fere.... : J c (-n) i lica.... r get-index i lisufere d 1 : K c (-n) i lica.... r get-index i lisufere d 2 : indexed-times c (nq-) i puduposw i lieq.... d 0 i lililiju r drop-pair r indexed-times.run r choose : indexed-times.run i lica.... r prepare i lica.... r indexed-times.inner i liju.... r cleanup ~~~ ~~~ : n:get c (-n) i lica.... r s:get/token i lica.... r s:temp i liju.... r s:to-n ~~~ ================================================================ # 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 i liadfe.. d 2 c if address is negative, it's immediate i dulilt.. d 0 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. i lifelicj r Compiler r comma i ju...... : handle-immediate c immediate words are always called. i limuju.. d -1 ~~~ ================================================================ 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. 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: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...... c check to see if the buffer is empty. If so, abort this. i lifelieq r sys:buffers/input d 0 i licj.... r s:get.loop 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 ~~~ ~~~ : ) c (-) i re...... ~~~ ~~~ : 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 ~~~ ~~~ c The $ sigil returns the first character in a string. : sigil:$ c (s-c) i lilica.. d 0 r a:fetch i liju.... r process-data ~~~ 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 d:create i lica.... r compiler:dtc i liju.... r compiler:on ~~~ 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 ~~~ ~~~ : sigil:@ c (s-a) i lica.... r d:lookup i lica.... r d:address i felife.. r Compiler i lililiju r sigil:@.compile r fetch r choose : sigil:@.compile c (a-) i lilica.. r lit r comma i lica.... r comma i liliju.. r fetch r comma ~~~ ~~~ : sigil:! c (ns-) i lica.... r d:lookup i lica.... r d:address i felife.. r Compiler i lililiju r sigil:!.compile r store r choose : sigil:!.compile c (a-) i lilica.. r lit r comma i lica.... r comma i liliju.. r store r comma ~~~ ~~~ : sigil:\ c (as-) i lica.... r d:create i lifelica r Latest r d:address i stre.... ~~~ ~~~ : depths c (-nn) (data, address) i liiore.. d 7 : depth/data i liiodrre d 7 : depth/address i liioswdr d 7 i re...... ~~~ ~~~ : {{ i lifeduli r Latest r sys:buffers/scope i lica.... r store-next i stre.... : ---reveal--- i lilica.. r scope.hidden r d:create i lifelili r Latest r sys:buffers/scope d 1 i adstre.. : scope.hidden s _ : }} i lilica.. r sys:buffers/scope r fetch-next i swfelica r d:link i stre.... ~~~ ~~~ : var c (s-) i lisw.... d 0 c fall through into var-n : var-n c (ns-) i lica.... r d:create i liju.... r comma : rot c (abc-bca) i puswposw i re...... : n:between? c (nlu-f) i puswposw i dupu.... i puswposw i puswposw i lica.... r n:limit i poeqre.. ~~~ # 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. 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 #3 n:add 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 3 i lica.... 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) c get the current Compiler state i lica.... r compiling? c begin compiling i lica.... r compiler:on c lay down a call to `internal:quote` i lilica.. r quote r comma c get a pointer to `here` for later use i lifelica r Free r tuck c store a dummy value for the length. This will be patched c by `]`. i lilica.. d 0 r comma c use `compiler:dtc` to lay down code tostart a DTC code c sequence i liju.... r compiler:dtc : ] c (a-a) c lay down a 0 to end the quote definition i lilica.. d 0 r comma c determine the length of the quotation i life.... r Free i puduposw i sulisu.. d 1 c update the dummy length with the actual length i swst.... c reset the compiler state to whatever it was prior c to invoking `[` i list.... r Compiler 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. i lifelili r Compiler r drop r interpreting-quote i liju.... r choose : 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 duliadsw d 1 i fe...... 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 popoduli d 1 i adswfesw i puswpure ~~~ This is exposed to the Forth dictionary as `internal:quote`. ~~~ : quote c (-p) i popoduli d 1 i adswfesw i dupuadpo i swpuswpu i re...... ~~~ # 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 ~~~ 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 c (n-) i lilica.. d 1 r comma i liju.... r comma : compile:call c (a-) i lilica.. i lica.... r comma i liju.... r comma : compile:jump c (a-) i lilica.. i liju.... r comma i liju.... r comma : curry c (vq-q) i lifepu.. r Free i swlica.. r compile:lit i lica.... r compile:jump i pore.... ~~~ # 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. 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 (-) c increment Next-String i lilica.. r Next-String r v:inc c compare Next-String to 8 i lilifeeq d 8 r Next-String c if 8, use "s:adjust" to reset the counter i liliju.. r s:adjust r if ~~~ `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 s:temp-pointer i lica.... r tuck i lica.... r a:copy i liju.... r s:next ~~~ :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) c compile a call to `data:string` i lilica.. r data:string r comma c get a reference to here, tuck it out of the way i lifesw.. r Free c get the string length, store it `here` i dulica.. r a:length i lica.... r comma c then use `a:for-each` to copy the characters to `here` i lilica.. r comma r a:for-each c finally, if compiling, drop the pointer from the stack. (If c not compiling, we just leave it on the stack) i lifelili r Compiler r drop r if i ju...... ~~~ `s:append` is similar to `a:append`, but specifically uses the temporary buffer memory. ~~~ : s:append c (ss-s) i lifepu.. r Free i lica.... r s:temp-pointer i listlica r Free r s:next i lica.... r a:append i polistre r Free : s:prepend c (ss-) i swliju.. r s:append ~~~ ~~~ : block:buffer c (-a) i lire.... r sys:buffers/block ~~~ ~~~ : e:to-line c (n-a) #64 n:mul block:buffer n:add ; i limuliad d 64 r sys:buffers/block i re.... : e:line c (n-) e:to-line #64 [ fetch-next c:put ] times drop nl ; i lica.... r e:to-line i lililica d 64 r e:line.inner r times i drliju.. r nl : e:line.inner i lica.... r fetch-next i liju.... r c:put : sep i lica.... r sp i lica.... r sp i lica.... r sp i lililica d 6 r ed.sep.mid r times i lilica.. r ed.sep.end$ r s:put i liju.... r nl : ed.sep.mid i liliju.. r ed.sep.mid$ r s:put : ed.sep.mid$ s +----5---- : ed.sep.end$ s +--- : l/n i lica.... r I i duliltli d 10 r sp i lica.. r if i lica.... r n:put i liju.... r sp : line i lica.... r l/n i lica.... r I i liju.... r e:line : list# : lines i lililiju d 16 r line r indexed-times : info i lilica.. r info$ r d:lookup i duline.. d 0 i lililiju r info.action r drop r choose : info.action i lica.... r d:address i feju.... : info$ s sys:info : list* c (-) i lililiju d 16 r list*.inner r indexed-times : list*.inner i lica.... r I i liju.... r e:line : list c (-) i lica.... r sep i lica.... r lines i lica.... r sep i liju.... r info : e:Display r list ~~~ ~~~ : ed.reset c #1024 block:buffer n:dec store ; i lililisu d 1024 r sys:buffers/block d 1 i stre.... : ed.constrain c @Block #0 @Blocks n:dec n:limit !Block ; i lifeli.. r Block d 0 i lifelisu r Blocks d 1 i lica.... r n:limit i listre.. r Block : set c (n-) !Block constrain ; i listliju r Block r ed.constrain : save c (-) @Block block:buffer block:save ; i lifeli.. r Block r sys:buffers/block i liju.... r block:save : load c (-) @Block block:buffer block:load reset ; i lifeli.. r Block r sys:buffers/block i lica.... r block:load i liju.... r ed.reset : next c (-) &Block v:inc constrain load ; i lilica.. r Block r v:inc i lica.... r ed.constrain i liju.... r load : prev c (-) &Block v:dec constrain load ; i lilica.. r Block r v:dec i lica.... r ed.constrain i liju.... r load : new c (-) #32 block:buffer #1024 fill reset ; i lilili.. d 32 r sys:buffers/block d 1024 i lica.... r fill i liju.... r ed.reset : edit c (n-) set load @e:Display call ; i lica.... r set i lica.... r load i lifeju.. r e:Display : run c (-) reset block:buffer n:dec s:evaluate ; i lica.... r ed.reset i lilisu.. r sys:buffers/block d 1 i liju.... r s:evaluate : use c (block) set load run ; i lica.... r set i lica.... r load i liju.... r run : using c (first,last) over n:sub swap use [ next run ] times ; i puduposw i suswlica r use i liliju.. r using.times r times : using.times i lica.... r next i liju.... r run ~~~ ~~~ : s:get/line.handle i dulieqpu d 8 i duposwli d 127 i eqorlili r s:get/line.handle:b/s r comma i liju.... r choose : s:get/line.handle:b/s i dr...... i lifelife r Free r s:get/line.Start i eqlicj.. r s:get/line.process i liliju.. d -1 r allot : s:get/line.process i dulieqli d 10 r s:get/line.process:done i liliju.. r s:get/line.process.handle r choose : s:get/line.process:done i drlire.. d -1 : s:get/line.process.handle i lica.... r s:get/line.handle i lire.... d 0 : s:get/line.Start d 0 : s:get/line c (-s) c here n:inc &s:get/line.Start store c here [ #0 comma [ c:get process ] until ] sip c here over n:sub n:dec over !Free swap store drop c here s:temp ; i lifeliad r Free d 1 i list.... r s:get/line.Start i lifedupu r Free i lilica.. d 0 r comma i lilica.. r s:get/line.read r until i polifepu r Free i duposwsu i lisupudu d 1 i poswlist r Free i swstdrli r Free i feliju.. r s:temp : s:get/line.read i lica.... r c:get i liju.... r s:get/line.process ~~~ ~~~ : e:erase/line c (n-) c e:to-line #32 swap #64 fill ; i lica... r e:to-line i liswli.. d 32 d 64 i liju.... r fill : e:replace c (sn-) c n:inc swap e:to-line over n:dec s:length copy ; i liadsw.. d 1 i lica.... r e:to-line i puduposw i lisulica d 1 r a:length i cyre.... : e:replace-at c (snn-) c [ &e:to-line dip n:add ] dip c [ over store n:inc ] s:for-each drop ; i pulilica r e:to-line r dip i adpolili r e:replace-at:internal r a:for-each i ca...... i drre.... : e:replace-at:internal i puduposw i stliadre d 1 : e:insert c (n"-) c dup e:erase/line s:get/line e:replace ; i dulica.. r e:erase/line i lica.... r s:get/line i liju.... r e:replace : e:insert-at c (nn"-) c s:get/line e:replace-at ; i lica.... r s:get/line i liju.... r e:replace-at : 0 c ("-) i liliju.. d 0 r e:insert : 1 c ("-) i liliju.. d 1 r e:insert : 2 c ("-) i liliju.. d 2 r e:insert : 3 c ("-) i liliju.. d 3 r e:insert : 4 c ("-) i liliju.. d 4 r e:insert : 5 c ("-) i liliju.. d 5 r e:insert : 6 c ("-) i liliju.. d 6 r e:insert : 7 c ("-) i liliju.. d 7 r e:insert : 8 c ("-) i liliju.. d 8 r e:insert : 9 c ("-) i liliju.. d 9 r e:insert : 10 c ("-) i liliju.. d 10 r e:insert : 11 c ("-) i liliju.. d 11 r e:insert : 12 c ("-) i liliju.. d 12 r e:insert : 13 c ("-) i liliju.. d 13 r e:insert : 14 c ("-) i liliju.. d 14 r e:insert : 15 c ("-) i liliju.. d 15 r e:insert ~~~ ~~~ : . c ("-) c #62 [ c:get drop ] times ; i lililiju d 62 r .:internal r times : .:internal i lica.... r c:get i drre.... ~~~ 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. ~~~ : needs.check c (-f) c block:buffer &sys:buffers/needs @Len compare ; i lililife r sys:buffers/block r sys:buffers/needs r needs.Len i cpre.... : needs.setup c (s-) c dup s:length !Len n:inc &sys:buffers/needs @Len copy ; i dulica.. r a:length i listliad r needs.Len d 1 i lilifecy r sys:buffers/needs r needs.Len i re...... : needs c (s-) c setup @Block [ c @Blocks [ I set load check &run if ] indexed-times c ] dip !Block load ; i lica.... r needs.setup i lifepu.. r Block i lifelili r Blocks r needs:internal r indexed-times i ca...... i polist.. r Block i liju.... r load : needs:internal i lica.... r I i lica.... r set i lica.... r load i lica.... r needs.check i liliju.. r run r if ~~~ 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 c (-) c @Block @Blocks c [ I set load block:buffer fetch #32 -eq? c [ I n:put sp #64 block:buffer n:dec &store &s:put bi nl ] if c ] indexed-times !Block load ; i lifelife r Block r Blocks i lilica.. r titles:inner r indexed-times i listliju r Block r load : titles:inner i lica.... r I i lica.... r set i lica.... r load i lifeline r sys:buffers/block d 32 i liliju.. r titles:display r if : titles:display i lica.... r I i lica.... r n:put i lica.... r sp i lililisu d 64 r sys:buffers/block d 1 i lililica r store r s:put r bi i liju.... r nl ~~~ `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 c (-) c '___B: s:put @Block n:put $/ c:put @Blocks n:dec n:put i lilica.. r sys:info:B$ r s:put i lifelica r Block r n:put i lilica.. d 47 r c:put i lifelisu r Blocks d 1 i lica.... r n:put c '___S: s:put depth/data n:put $/ c:put #32 n:put i lilica.. r sys:info:S$ r s:put i lica.... r depth/data i lica.... r n:put i lilica.. d 47 r c:put i lilica.. d 32 r n:put c '___M: s:put here n:put $/ c:put #59999 n:put nl ; i lilica.. r sys:info:M$ r s:put i lifelica r Free r n:put i lilica.. d 47 r c:put i lilica.. d 59999 r n:put i liju.... r nl : sys:info:B$ s B: : sys:info:S$ s S: : sys:info:M$ s M: ~~~ ================================================================ 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. ~~~ : ~startup:process c (n-f) i lica.... r set i lica.... r load i lifelieq r sys:buffers/block d 40 i liliju.. r run r if : prelude i lilica.. d 1 r ~startup:process i liliju.. d 2 r ~startup:process : startup i liju.... r prelude : rom:save c (-) i liiore.. d 4 ~~~ ================================================================ EOF ~~~ : ENTRY.0 d 0 d 177614 r ) : ENTRY.1 r ENTRY.0 d -1644352334 r ---reveal--- : ENTRY.2 r ENTRY.1 d 2088204551 r neq? : ENTRY.3 r ENTRY.2 d 193429569 r -if : ENTRY.4 r ENTRY.3 d 177619 r . : ENTRY.5 r ENTRY.4 d 177621 r 0 : ENTRY.6 r ENTRY.5 d 177622 r 1 : ENTRY.7 r ENTRY.6 d 5861574 r 10 : ENTRY.8 r ENTRY.7 d 5861575 r 11 : ENTRY.9 r ENTRY.8 d 5861576 r 12 : ENTRY.10 r ENTRY.9 d 5861577 r 13 : ENTRY.11 r ENTRY.10 d 5861578 r 14 : ENTRY.12 r ENTRY.11 d 5861579 r 15 : ENTRY.13 r ENTRY.12 d 177623 r 2 : ENTRY.14 r ENTRY.13 d 177624 r 3 : ENTRY.15 r ENTRY.14 d 177625 r 4 : ENTRY.16 r ENTRY.15 d 177626 r 5 : ENTRY.17 r ENTRY.16 d 177627 r 6 : ENTRY.18 r ENTRY.17 d 177628 r 7 : ENTRY.19 r ENTRY.18 d 177629 r 8 : ENTRY.20 r ENTRY.19 d 177630 r 9 : ENTRY.21 r ENTRY.20 d 177632 R ; : ENTRY.22 r ENTRY.21 d 212805696 r ?jump : ENTRY.23 r ENTRY.22 d 67966955 r BaseBlock : ENTRY.24 r ENTRY.23 d 216428464 r Block : ENTRY.25 r ENTRY.24 d -1447795165 r Blocks : ENTRY.26 r ENTRY.25 d -1210660288 r Compiler : ENTRY.27 r ENTRY.26 d 1264838491 r Latest : ENTRY.28 r ENTRY.27 d 2089116775 r Free : ENTRY.29 r ENTRY.28 d 177646 r I : ENTRY.30 r ENTRY.29 d 177647 r J : ENTRY.31 r ENTRY.30 d 177648 r K : ENTRY.32 r ENTRY.31 d -786332176 r Sigils : ENTRY.33 r ENTRY.32 d 177664 R [ : ENTRY.34 r ENTRY.33 d 177666 R ] : ENTRY.35 r ENTRY.34 d -296263550 r a:-eq? : ENTRY.36 r ENTRY.35 d 1539635992 r a:append : ENTRY.37 r ENTRY.36 d 1565438329 r a:behead : ENTRY.38 r ENTRY.37 d -294319702 r a:chop : ENTRY.39 r ENTRY.38 d 63806334 r a:contains? : ENTRY.40 r ENTRY.39 d -294312037 r a:copy : ENTRY.41 r ENTRY.40 d 251383785 r a:dup : ENTRY.42 r ENTRY.41 d 251384693 r a:eq? : ENTRY.43 r ENTRY.42 d -1119160502 r a:fetch : ENTRY.44 r ENTRY.43 d 1726883814 r a:filter : ENTRY.45 r ENTRY.44 d -1119018392 r a:first : ENTRY.46 r ENTRY.45 d -1309732155 r a:for-each : ENTRY.47 r ENTRY.46 d -294147516 r a:hash : ENTRY.48 r ENTRY.47 d -1115296648 r a:index : ENTRY.49 r ENTRY.48 d 917819423 r a:indices : ENTRY.50 r ENTRY.49 d -294003756 r a:last : ENTRY.51 r ENTRY.50 d -293999829 r a:left : ENTRY.52 r ENTRY.51 d 1957010690 r a:length : ENTRY.53 r ENTRY.52 d -293968098 r a:make : ENTRY.54 r ENTRY.53 d -1751031389 r a:make/temp : ENTRY.55 r ENTRY.54 d 251392926 r a:map : ENTRY.56 r ENTRY.55 d 2000526863 r a:middle : ENTRY.57 r ENTRY.56 d 1526142126 r a:prepend : ENTRY.58 r ENTRY.57 d -2103488936 r a:reduce : ENTRY.59 r ENTRY.58 d -674869668 r a:reverse : ENTRY.60 r ENTRY.59 d -1104799682 r a:right : ENTRY.61 r ENTRY.60 d -1103209427 r a:store : ENTRY.62 r ENTRY.61 d -293712106 r s:temp : ENTRY.63 r ENTRY.62 d 2090026588 r a:th : ENTRY.64 r ENTRY.63 d 253189153 r allot : ENTRY.65 r ENTRY.64 d 193486360 r and : ENTRY.66 r ENTRY.65 d 5863248 r bi : ENTRY.67 r ENTRY.66 d 193487226 r bi* : ENTRY.68 r ENTRY.67 d 193487248 r bi@ : ENTRY.69 r ENTRY.68 d 1122748452 r block:buffer : ENTRY.70 r ENTRY.69 d 427330826 r block:load : ENTRY.71 r ENTRY.70 d 427567833 r block:save : ENTRY.72 r ENTRY.71 d 193487813 r bye : ENTRY.73 r ENTRY.72 d 253758370 r c:get : ENTRY.74 r ENTRY.73 d -157167450 r c:lowercase? : ENTRY.75 r ENTRY.74 d 253768699 r c:put : ENTRY.76 r ENTRY.75 d 153339739 r c:to-lower : ENTRY.77 r ENTRY.76 d -215432539 r c:to-s : ENTRY.78 r ENTRY.77 d 164041342 r c:to-upper : ENTRY.79 r ENTRY.78 d 430999977 r c:uppercase? : ENTRY.80 r ENTRY.79 d 2090140673 r call : ENTRY.81 r ENTRY.80 d -161057562 r choose : ENTRY.82 r ENTRY.81 d 255669810 r comma : ENTRY.83 r ENTRY.82 d -748339476 r compare : ENTRY.84 r ENTRY.83 d 425733796 r compile:call : ENTRY.85 r ENTRY.84 d 426007172 r compile:jump : ENTRY.86 r ENTRY.85 d -898142575 r compile:lit : ENTRY.87 r ENTRY.86 d -1979274138 r compiling? : ENTRY.88 r ENTRY.87 d 2090156064 r copy : ENTRY.89 r ENTRY.88 d 255891066 r curry : ENTRY.90 r ENTRY.89 d 352952457 r d:address : ENTRY.91 r ENTRY.90 d 626189207 r d:create : ENTRY.92 r ENTRY.91 d 2012546722 r d:exists? : ENTRY.93 r ENTRY.92 d -176741337 r d:hash : ENTRY.94 r ENTRY.93 d -176589039 r d:link : ENTRY.95 r ENTRY.94 d 975220285 r d:lookup : ENTRY.96 r ENTRY.95 d -1182620049 r depth/address : ENTRY.97 r ENTRY.96 d -214216093 r depth/data : ENTRY.98 r ENTRY.97 d -125438899 r depths : ENTRY.99 r ENTRY.98 d 193489474 r dip : ENTRY.100 r ENTRY.99 d 2090195226 r drop : ENTRY.101 r ENTRY.100 d 288947475 r drop-pair : ENTRY.102 r ENTRY.101 d 193489824 r dtc : ENTRY.103 r ENTRY.102 d 193489870 r dup : ENTRY.104 r ENTRY.103 d -59285433 r dup-pair : ENTRY.105 r ENTRY.104 d -572166886 r e:Display : ENTRY.106 r ENTRY.105 d 1606468171 r e:erase/line : ENTRY.107 r ENTRY.106 d 525535321 r e:insert : ENTRY.108 r ENTRY.107 d 1191682587 r e:insert-at : ENTRY.109 r ENTRY.108 d -137453652 r e:line : ENTRY.110 r ENTRY.109 d -1454437472 r e:replace : ENTRY.111 r ENTRY.110 d 1632613378 r e:replace-at : ENTRY.112 r ENTRY.111 d 1440404764 r e:to-line : ENTRY.113 r ENTRY.112 d 2090215723 r edit : ENTRY.114 r ENTRY.113 d 193490778 r eq? : ENTRY.115 r ENTRY.114 d 258875503 r fetch : ENTRY.116 r ENTRY.115 d -1885660229 r fetch-next : ENTRY.117 r ENTRY.116 d 2090257196 r fill : ENTRY.118 r ENTRY.117 d -1163346114 r forever : ENTRY.119 r ENTRY.118 d 5863407 r gc : ENTRY.120 r ENTRY.119 d 193493055 r gt? : ENTRY.121 r ENTRY.120 d 260584565 r gteq? : ENTRY.122 r ENTRY.121 d 2090324905 r here : ENTRY.123 r ENTRY.122 d 5863476 r if : ENTRY.124 r ENTRY.123 d 123652725 r indexed-times : ENTRY.125 r ENTRY.124 d -1223977595 r lit : ENTRY.126 r ENTRY.125 d -1465379862 r quote : ENTRY.127 r ENTRY.126 d 314257922 r interpret : ENTRY.128 r ENTRY.127 d 5863485 r io : ENTRY.129 r ENTRY.128 d 2090414049 r jump : ENTRY.130 r ENTRY.129 d 2090473057 r list : ENTRY.131 r ENTRY.130 d 266134180 r list# : ENTRY.132 r ENTRY.131 d 266134187 r list* : ENTRY.133 r ENTRY.132 d 2090478981 r load : ENTRY.134 r ENTRY.133 d 193498500 r lt? : ENTRY.135 r ENTRY.134 d 266514170 r lteq? : ENTRY.136 r ENTRY.135 d -494948871 r n:-zero? : ENTRY.137 r ENTRY.136 d 266796867 r n:abs : ENTRY.138 r ENTRY.137 d 266796918 r n:add : ENTRY.139 r ENTRY.138 d 1032861494 r n:between? : ENTRY.140 r ENTRY.139 d 266800217 r n:dec : ENTRY.141 r ENTRY.140 d 266800368 r n:div : ENTRY.142 r ENTRY.141 d 1637942608 r n:divmod : ENTRY.143 r ENTRY.142 d 266803501 r n:get : ENTRY.144 r ENTRY.143 d 266805959 r n:inc : ENTRY.145 r ENTRY.144 d -1502694228 r n:limit : ENTRY.146 r ENTRY.145 d 266809907 r n:max : ENTRY.147 r ENTRY.146 d 266810161 r n:min : ENTRY.148 r ENTRY.147 d 266810349 r n:mod : ENTRY.149 r ENTRY.148 d 266810555 r n:mul : ENTRY.150 r ENTRY.149 d 2024000897 r n:negate : ENTRY.151 r ENTRY.150 d 266813830 r n:put : ENTRY.152 r ENTRY.151 d 266817079 r n:sub : ENTRY.153 r ENTRY.152 d 215056784 r n:to-s : ENTRY.154 r ENTRY.153 d -1486229492 r n:zero? : ENTRY.155 r ENTRY.154 d 268346580 r needs : ENTRY.156 r ENTRY.155 d 193500239 r new : ENTRY.157 r ENTRY.156 d 2090540740 r next : ENTRY.158 r ENTRY.157 d 193500364 r nip : ENTRY.159 r ENTRY.158 d 5863647 r nl : ENTRY.160 r ENTRY.159 d 193500566 r not : ENTRY.161 r ENTRY.160 d 5863686 r or : ENTRY.162 r ENTRY.161 d 2090594561 r over : ENTRY.163 r ENTRY.162 d 193502740 r pop : ENTRY.164 r ENTRY.163 d -1031328682 r prelude : ENTRY.165 r ENTRY.164 d 2090626146 r prev : ENTRY.166 r ENTRY.165 d -853324053 r process-data : ENTRY.167 r ENTRY.166 d 2090629861 r push : ENTRY.168 r ENTRY.167 d 1059716234 r restart : ENTRY.169 r ENTRY.168 d 337707900 r rom:save : ENTRY.170 r ENTRY.169 d 193504922 r rot : ENTRY.171 r ENTRY.170 d 193505114 r run : ENTRY.172 r ENTRY.171 d 408173524 r a:-eq? : ENTRY.173 r ENTRY.172 d -127536406 r s:append : ENTRY.174 r ENTRY.173 d -101734069 r a:behead : ENTRY.175 r ENTRY.174 d 410117372 r a:chop : ENTRY.176 r ENTRY.175 d 1683118608 r a:contains? : ENTRY.177 r ENTRY.176 d 410125037 r a:copy : ENTRY.178 r ENTRY.177 d 272730363 r a:dup : ENTRY.179 r ENTRY.178 d 272731271 r a:eq? : ENTRY.180 r ENTRY.179 d 102250697 r s:evaluate : ENTRY.181 r ENTRY.180 d 652426460 r a:fetch : ENTRY.182 r ENTRY.181 d 59711416 r a:filter : ENTRY.183 r ENTRY.182 d 652568570 r a:first : ENTRY.184 r ENTRY.183 d -89307369 r a:for-each : ENTRY.185 r ENTRY.184 d -369411895 r s:get/line : ENTRY.186 r ENTRY.185 d 704009186 r s:get/token : ENTRY.187 r ENTRY.186 d 410289558 r a:hash : ENTRY.188 r ENTRY.187 d 1735582460 r a:index : ENTRY.189 r ENTRY.188 d 1735705137 r a:indices : ENTRY.190 r ENTRY.189 d 410401271 r s:keep : ENTRY.191 r ENTRY.190 d 410433318 r a:last : ENTRY.192 r ENTRY.191 d 410437245 r a:left : ENTRY.193 r ENTRY.192 d 289838292 r a:length : ENTRY.194 r ENTRY.193 d 272739504 r a:map : ENTRY.195 r ENTRY.194 d 333354465 r a:middle : ENTRY.196 r ENTRY.195 d -1950939456 r s:prepend : ENTRY.197 r ENTRY.196 d 272743435 r s:put : ENTRY.198 r ENTRY.197 d 524305962 r a:reduce : ENTRY.199 r ENTRY.198 d 143016046 r a:reverse : ENTRY.200 r ENTRY.199 d 144659380 r s:rewrite : ENTRY.201 r ENTRY.200 d 666787280 r a:right : ENTRY.202 r ENTRY.201 d 668377535 r a:store : ENTRY.203 r ENTRY.202 d 410724968 r s:temp : ENTRY.204 r ENTRY.203 d 2090673454 r a:th : ENTRY.205 r ENTRY.204 d 1238161771 r s:to-lower : ENTRY.206 r ENTRY.205 d 410733744 r s:to-n : ENTRY.207 r ENTRY.206 d 1248863374 r s:to-upper : ENTRY.208 r ENTRY.207 d 2090715988 r save : ENTRY.209 r ENTRY.208 d 193505681 r set : ENTRY.210 r ENTRY.209 d -38720901 r shift-left : ENTRY.211 r ENTRY.210 d -1270529650 r shift-right : ENTRY.212 r ENTRY.211 d -1801857832 r sigil:! : ENTRY.213 r ENTRY.212 d -1801857830 r sigil:# : ENTRY.214 r ENTRY.213 d -1801857829 r sigil:$ : ENTRY.215 r ENTRY.214 d -1801857827 r sigil:& : ENTRY.216 r ENTRY.215 d -1801857826 r sigil:' : ENTRY.217 r ENTRY.216 d -1801857825 r drop : ENTRY.218 r ENTRY.217 d -1801857807 r sigil:: : ENTRY.219 r ENTRY.218 d -1801857801 r sigil:@ : ENTRY.220 r ENTRY.219 d -1801857773 r sigil:\ : ENTRY.221 r ENTRY.220 d 576954903 r sigil:get : ENTRY.222 r ENTRY.221 d 576967971 r sigil:set : ENTRY.223 r ENTRY.222 d 193505809 r sip : ENTRY.224 r ENTRY.223 d 5863816 r sp : ENTRY.225 r ENTRY.224 d -1378149864 r startup : ENTRY.226 r ENTRY.225 d 274826578 r store : ENTRY.227 r ENTRY.226 d 1976567422 r store-next : ENTRY.228 r ENTRY.227 d 2090739264 r swap : ENTRY.229 r ENTRY.228 d -1371592987 r sys:buffers/block : ENTRY.230 r ENTRY.229 d -1363217974 r sys:buffers/input : ENTRY.231 r ENTRY.230 d -1359625529 r sys:buffers/loops : ENTRY.232 r ENTRY.231 d -1357624343 r sys:buffers/needs : ENTRY.233 r ENTRY.232 d 942827360 r sys:buffers/numeric-conversion : ENTRY.234 r ENTRY.233 d 1106725658 r sys:buffers/reserved : ENTRY.235 r ENTRY.234 d -1351755340 r sys:buffers/scope : ENTRY.236 r ENTRY.235 d 1438625409 r sys:buffers/strings+arrays : ENTRY.237 r ENTRY.236 d 1558645459 r sys:buffers/variables : ENTRY.238 r ENTRY.237 d 270722346 r sys:info : ENTRY.239 r ENTRY.238 d 193506620 r tab : ENTRY.240 r ENTRY.239 d 275614599 r times : ENTRY.241 r ENTRY.240 d 505606010 r titles : ENTRY.242 r ENTRY.241 d 193507188 r tri : ENTRY.243 r ENTRY.242 d 2090769950 r tri* : ENTRY.244 r ENTRY.243 d 2090769972 r tri@ : ENTRY.245 r ENTRY.244 d 2090773084 r tuck : ENTRY.246 r ENTRY.245 d 276987953 r until : ENTRY.247 r ENTRY.246 d 193508306 r use : ENTRY.248 r ENTRY.247 d 277155819 r using : ENTRY.249 r ENTRY.248 d 276287585 r v:dec : ENTRY.250 r ENTRY.249 d 276293327 r v:inc : ENTRY.251 r ENTRY.250 d 193508814 r var : ENTRY.252 r ENTRY.251 d 277702537 r var-n : ENTRY.253 r ENTRY.252 d 279132286 r while : ENTRY.254 r ENTRY.253 d 193511454 r xor : ENTRY.255 r ENTRY.254 d 5864091 r {{ : ENTRY.256 r ENTRY.255 d 5864159 r }} : Latest r ENTRY.256 : FREE-SPACE ~~~