4word is a stack language, similar to Forth.
4word was designed to be used as a command interpreter over text chat in Common Lisp systems. Programming a stack language over text chat is quite convenient, as complicated expressions may be divided into smaller pieces and intermediate results may be inspected and altered. 4word has been used with Jabber (XMPP) and Microsoft Teams (Microsoft Bot Framework) to set parameters, read system status etc.
The interpreter state is easily serialized and stored in a file system or a database management system, and adding new words from the lisp environment to the vocabulary is easy, too.
The special variables *WORDS* and *STACK* must be bound around calls to the outer interpreter INTERPRET. Hence, the simplest use of the interpreter may look something like this.
(let ((*words* ...) (*stack* nil)) (interpret "1 1 +") *stack*) ;; The above returns stack: (2)
Another, more realistic example involves a user's vocabulary and stack. It is the responsibility of the application to load and save these two around calls to the interpreter.
(let ((*words* user-vocabulary) (*stack* user-stack)) (interpret message-text :max-cycles 1000) (setf user-vocabulary *words* user-stack *stack*))
The vocabulary are divided into categories in this document for better browsability.
Words, ordered by name.
Detailed description of the vocabulary, one word per "box". The header includes word name, stack effect and type. A word may be of primitive or threaded type. Some documentation and examples may be provided in addition to the header. The documentation has been colorized to highlight keywords from the stack comment, where consumed objects are of red color and and produced objects are of green color.
Creates a VECTOR of length LENGTH.
Creates an immutable virtual sequence (IOTA) containing the integers from 0 to LENGTH-1.
For each number from 0 to N-1, place the number on top of stack and call QUOT.
! Times is just shorthand for EACH over a <IOTA>. 3 [ inc ] times ! Stack: 1 2 3 ! The same effect as: 3 <iota> [ inc ] each
For each value from ITERABLE, place value on top of stack and call QUOT.
[ 1 2 3 ] [ inc ] each ! Stack: 2 3 4
For each value from ITERABLE, place value on top of stack and call QOUT, then collect the result into VECTOR.
[ 1 2 3 ] [ inc ] map ! Stack: [ 2 3 4 ]
For each value from ITERABLE, place value on top of stack and call QUOT. Collect the value into VECTOR if QUOT returned non-NIL.
[ 1 2 3 ] [ 3 < ] filter ! Stack: [ 1 2 ]
Aggregate values from ITERABLE as AGGREGATE using QUOT. The first value from ITERABLE is combined with IDENTITY.
[ 1 2 3 ] 0 [ + ] reduce ! Stack: 6
Words may be added to the vocabulary by traditional Forth colon-definitions.
Enter the compiler and compile until a semicolon, then add the compiled word on top of the dictionary.
: factorial ( n -- n! ) <iota> [ 1 + ] map 1 [ * ] reduce ;
Variables are dynamically bound in frames on the return stack. When a word exists, it's variable bindings goes out of scope.
Allocate VARIABLE and bind to VALUE.
"HELLO" , text let
Bind an already allocated VARIABLE to VALUE.
"HI" , text let
Get the VALUE bound to VARIABLE.
, text get
This category contains a mix of high and low level words. IF and , are frequent in user code while CALL, RBUN and especially OBUN is less common.
Push OBJECT at program counter + 1 onto the stack.
, test ! Stack: TEST
Call quotation QUOT.
If TEST is non-NIL call THEN-QUOT else call ELSE-QUOT.
0 1 < [ , true ] [ , false ] if ! Stack: TRUE
Reset program counter unless X is NIL.
Offset program counter by OFFSET unless X is NIL.
These words form the foundation upon which the higher level sequence words (MAP, REDUCE etc.) are built. Vectors are the most common kind of sequence in user code, while linked lists (cons cells) are used internally by many of the higher level words.
Get the LENGTH of sequence SEQ.
Set the OBJECT at index INDEX in a sequence SEQ.
Get the OBJECT at index INDEX in a sequence SEQ.
Creates a CONS from CDR and CAR.
Get CAR of CONS.
Get CDR of CONS.
Deconstruct CONS as CDR and CAR.
Creates an ITERATOR to iterate over ITERABLE. In fact, ITERATOR is just a simple state-object designed to be used by ITERATE.
Get the next VALUE from the ITERATOR. In addition to the VALUE, push a VALUE-P boolean. VALUE-P is NIL when the iteration is complete.
Returns T if X and Y are structurally similar (isomorphic) objects, else returns NIL. Common Lisp's EQUAL function is used to compare the objects.
The vocabulary for number arithmetics and comparisons are far from complete.
The most popular stack shuffling words are built in as primitives.