Skip to content

Latest commit

 

History

History
283 lines (241 loc) · 11.1 KB

quartet.md

File metadata and controls

283 lines (241 loc) · 11.1 KB

Quartet

There are many possible models for describing an actor's behavior. One simple model is an imperative stack-oriented machine with a dictionary (similar to FORTH).

Program source is provided as a stream of words (whitespace separated in text format). If the word parses as a number the value is pushed on the data stack. Otherwise the word is looked up in the current dictionary. If the associated value is a block it is executed, otherwise the value is pushed on the data stack. The data stack holds parameters for executing blocks and their return values. Some blocks also consume words from the source stream.

An actor's behavior is described with a block. The message received by the actor is the contents of the data stack. The SEND primitive sends the current stack contents, clearing the stack. Values may be saved in the dictionary by binding them to a word. All dictionary changes are local to the executing behavior.

Implementation

Blocks are essentially functional closures. Executing a word definition is like applying the function to the parameters on the stack (consuming them). Result values (if any) are left on the stack.

The data stack contains universal integer values, usually interpreted as signed 2's-complement numbers. Numeric operations do not overflow, but rather wrap around forming a ring, which may be interpreted as either signed or unsigned. The number of bits is not specified.

The quartet program TRUE 1 LSR DUP NOT .? .? prints the minimum and maximum signed value representations.

Tagged Types

Program source is represented by only two types, Words and Numbers. Values on the data stack can have richer types. Stack values can be Words, Numbers, Blocks, Procedures, and Actors (Boolean values are just Numbers). Any value that can appear on the stack can also be bound to a Word in the dictionary.

Primitive Dictionary

The following primitive definitions are assumed to be present in the initial dictionary.

Actor Primitives

Input Operation Output Description
block CREATE actor Create a new actor with block behavior
...message actor SEND Send message to actor
block BECOME Replace current actor's behavior with block
SELF actor Push the current actor's address on the data stack
FAIL Abort processing and revert to prior state
STEP bool Dispatch an actor message, report success
RUN Dispatch all actor messages

Dictionary and Quoting

Input Operation Output Description
value = word Bind value to word in the current dictionary
' word word Push (literal) word on the data stack
@ word value Lookup value bound to word in the current dictionary
[ ... ] block Create block (quoted) value
[ ... ( ... ) [ ... value Immediate (unquoted) value

Boolean Conditionals

Input Operation Output Description
TRUE TRUE All bits set (1)
FALSE FALSE All bits clear (0)
value ZERO? bool TRUE if value = 0; otherwise FALSE
bool IF [ ] Execute block based on condition
bool IF-ELSE [ ] [ ] Choose block based on condition
bool WHILE [ ] Execute block while condition holds

Stack Manipulation

Input Operation Output Description
v DROP Drop the top element
v DUP v v Duplicate the top element (same as 1 PICK)
v2 v1 SWAP v1 v2 Swap the top two elements (same as 2 ROLL)
vn ... v1 n PICK vn ... v1 vn Duplicate element n
vn ... v1 n ROLL vn-1 ... v1 vn Rotate stack elements (negative for reverse)
DEPTH n Number of items on the data stack

Numeric and Logical Operations

Input Operation Output Description
INF INF Infinity/Undefined (only MSB set)
n NEG -n Numeric negation (2's complement)
n m ADD n+m Numeric addition
n m SUB n-m Numeric subtraction
n m MUL n*m Numeric multiplication
n m DIVMOD r q Numeric division/modulus
c b a FMA a*b+c Fused multiply/add
n m COMPARE n-m Compare numeric values
n LT? bool TRUE if n < 0; otherwise FALSE
n EQ? bool TRUE if n = 0; otherwise FALSE
n GT? bool TRUE if n > 0; otherwise FALSE
n NOT ~n Bitwise not (inversion)
n m AND n&m Bitwise and
n m OR n|m Bitwise or
n m XOR n^m Bitwise xor
n m LSL n<<m Logical shift left
n m LSR n>>m Logical shift right
n m ASR n>>m Arithmetic shift right (sign-extend)

Direct Memory Access

Input Operation Output Description
address ? value Load value from address
value address ! Store value into address
address ?? value Atomic load value from volatile address
value address !! Atomic store value into volatile address
address count DUMP Hex dump count values from address

Interactive Features

Input Operation Output Description
WORDS Print list of defined words
USAGE Print resource usage report
code EMIT Print ascii character code
... Print stack contents (non-destructive)
value .? Print internal representation of value
value . Print value

Examples

Print countdown from 5 to 1

5 DUP GT? WHILE [ DUP . 1 SUB DUP GT? ] DROP

Clear the stack

# ... CLEAR --
[ DEPTH GT? WHILE [ DROP DEPTH GT? ] ] = CLEAR

Print symbol based on numeric sign

# n CMP --
[ DUP EQ? IF-ELSE [ DROP ' = . ] [ DUP LT? IF [ ' < . ] GT? IF [ ' > . ] ] ] = CMP

Recursive factorial

# n fact n!
[ DUP ZERO? IF-ELSE [ DROP 1 ] [ DUP 1 SUB fact MUL ] ] = fact

Mutually-recursive even/odd

# n even? bool
[ DUP ZERO? IF-ELSE [ DROP TRUE ] [ 1 SUB odd? ] ] = even?
# n odd? bool
[ DUP ZERO? IF-ELSE [ DROP FALSE ] [ 1 SUB even? ] ] = odd?

Curried function constructor

# n \n+ block
[ = n [ n ADD ] ] = \n+
+1 \n+ = 1+  # increment
-1 \n+ = 1-  # decrement

Actor that displays any message it receives

[ ... 10 EMIT ] CREATE = println
' Hello ' World println SEND
STEP .

Actor that prints the number of messages it receives

[ = n [ n 1 ADD DUP . count_beh BECOME ] ] = count_beh
0 count_beh CREATE = counter
counter SEND
counter SEND
counter SEND
RUN

Serial-Port Echo Driver

[ ] = sink_beh 
@ sink_beh CREATE = sink

[ # cust
  UART0_RXDATA ??  # read UART receive fifo status/data
  DUP LT? IF-ELSE [
    DROP
    SELF SEND  # retry
  ] [
    16#FF AND SWAP SEND
  ]
] DUP = serial_read_beh CREATE = serial_read

[ # cust octet
  UART0_TXDATA ??  # read UART transmit fifo status
  LT? IF-ELSE [
    SELF SEND  # retry
  ] [
    UART0_TXDATA !!  # write UART fifo data
    SELF SWAP SEND
  ]
] DUP = serial_write_beh CREATE = serial_write

[ # octet
  DUP = octet 16#0D COMPARE
  EQ? IF [
    ' Stop dispatcher SEND
  ]
  SELF octet serial_write SEND
  @ serial_busy_beh BECOME
] = serial_echo_beh
[ # $serial_write
  serial_write COMPARE
  EQ? IF [
    SELF serial_read SEND
    @ serial_echo_beh BECOME
  ]
] = serial_busy_beh
@ serial_echo_beh CREATE = serial_echo

[ # output
  = output
  [ # cust octet
    SWAP = cust
    SELF SWAP output SEND
    SELF cust SEND
    output empty-Q serial_buffer_beh BECOME
  ]
] = serial_empty_beh
[ # output queue
  = queue
  = output
  [ # $output | cust octet
    DUP output COMPARE
    EQ? IF-ELSE [ # $output
      DROP
      queue Q-empty IF-ELSE [
        output serial_empty_beh BECOME
      ] [
        queue Q-TAKE
        output SWAP serial_buffer_beh BECOME
        SELF SWAP SEND
      ]
    ] [ # cust octet
      queue Q-PUT
      output SWAP serial_buffer_beh BECOME
      SELF SWAP SEND
    ]
  ]
] = serial_buffer_beh
serial_write serial_empty_beh CREATE = serial_buffer

serial_echo serial_read SEND  # start echo listen-loop