Skip to content
Tom Szczesny edited this page Aug 19, 2013 · 26 revisions

Data

typedef struct k0{I c,t,n;struct k0*k[1];}*K; //main K object

c is the reference count, the number of references pointing to the object. Initialized to 1.
t is the type of data stored, in [-4,7]
n is the number of elements stored
k is the array. It is initially typed for pointers to other K objects but accessors will cast it as an array of the proper type.

Each K object is allocated as a single contiguous block of memory. K pointers actually point to arbitrarily sized blocks of memory and not fixed sized chunks as the struct might otherwise suggest. The array being positioned at the end of the struct lets you index into arrays of arbitrary size and type. You can ignore the starting size of the array and index into the memory as far as necessary, relying on the product of n and the size of the type to indicate the bound. K objects of type 3 and -3 have a terminating null byte after the n-th byte. The just-allocated integer vector consisting of 7 8 9 is stored contiguously in memory as the 48 byte structure [1,-1,3,7,8,9]. Each number represents a 64-bit or 8-byte integer.

Type 7

Verbs, functions, and other executable forms of data are stored with type 7. All type 7’s have the same fixed size, TYPE_SEVEN_SIZE. The n field is instead used to store the subtype. The type 7 array is a series of void pointers to the first 8 of the following elements.

enum TYPE_SEVEN_MEMBERS {CONTEXT,DEPTH,CODE,LOCALS,PARAMS,CONJ,CACHE_WD,CACHE_TREE,TYPE_SEVEN_SIZE};
Name Type Explanation
CONTEXT interned string (sp()) Where verb was created on K-Tree
CODE (see subtype table) Various executable data
LOCALS dictionary References for function constants, 1 in (1+/)
PARAMS dictionary Track a b c in {[a;b] c:a+b}
CONJ type-7 or type-0 with allowed nulls Attached values for projections, 2 in +[2;]
n Subtype CODE type
0 unexecuted types, wd(), 1+1 -4
1 verb/derived verb 1+\ -4
2 dynamically loaded function -4 (always size 3)
3 brace function {x} -3
4 :[] -4
5 if[] -4
6 while[] -4
7 do[] -4

Execution

You read K left-to-right, but K executes right-to-left. K executes strictly right to left. It does not obey the common order of operations rule.

  2*3+4   /14 not 10

More precisely, K executes “left-of-right,” meaning that operations can be thought of as functions composed in the following way. If x is data, and g and f are functions, then “gfx” is interpreted g(f(x)).

  -|!4

This expression is read “negate reverse til four.” It is evaluated in the following fashion: generate a vector containing the integers zero through four (!), reverse the vector (|), then negate that (-).

You’ll notice that we have used two types of verbs so far: verbs which take one argument (monadic), and verbs which take two arguments (dyadic). The + and the * in the first example were instances of dyadic verbs. The -, |, and ! in the second example were instances of monadic verbs.

Whether a verb is dyadic or monadic is context dependent, and usually determined at parse time. The operators do different things depending on whether they are called in a monadic or dyadic fashion. The * symbol, for instance, means ‘times’ when used in a dyadic fashion. When used in a monadic fashion it means ‘first.’

    2*7      /times, dyadic
  14
 
    *8 9 10   /first, monadic
  8

You can force a verb to be monadic by adding a colon after it.

  5 6 7*0 1 2      /times
0 6 14

  5 6 7 *:0 1 2    /first, which yields zero, then 5 6 7 is indexed into with 0, which yields 5
5

The addition of adverbs complicates the execution process.

At the core of K is the ex() function which executes objects returned by the parser wd(). It has two fundamental behaviors. Both are recursive. They correspond to whether an encountered operation is dyadic or monadic. When a dyadic operation is encountered, wait with the left argument and the operation itself, and build the right argument by applying ex() to the sequence remaining to the right of the operation. When a monadic operation is encountered, wait with that operation, and then apply it to the result of applying ex() to the sequence to the right of that operation. There is no left argument to speak of. These two basic behaviors are modified to account for adverbs when adverbs are present.

The entries in a code array from a type 7 subtype 0 object will represent the tokens from the expression. When the expression

  1 2 3 + 4 5 6

is parsed, it returns a null-terminated code array with three non-zero symbols. The lists are represented by pointers to pointers. The verb is represented by an integer. The first list is a pointer to a pointer to a block of memory containing [1,-1,3,1,2,3]. The verb is an integer index into a lookup table containing the C plus function that accepts two arguments. The second list is a pointer to a pointer to a block of memory containing [1,-1,3,4,5,6]. The ex() function handles reading the pointers and applying them in the correct order. At run-time we must distinguish between verb/adverb integers and object pointers-to-pointers. See VA() for more.

The reason behind using pointers to pointers for objects, and not just pointers, is that data stored in variables can change during the execution process, and so the address of where that data is stored may need to change as well.