On this page:
1.1 Introduction
1.2 Actor Basic Message
amsg
make-amsg
1.3 Actor Basic Generics
gen:  abasic-actor
abasic-actor-send!
abasic-actor-recv!
1.4 Actor Basic Steps
abasic-step?
reply
debug
1.5 Actor Basic Expander
cond
else
case
and
or
when
not
loop
recv
recv-case
recv-case*
reply
with-recv
with-recv-case
abasic-lambda
1.6 Actor Basic Flow Graphs
abasic-graph-print
1.7 Actor Basic Define
define-abasic
define/  provide-abasic
1.8 Scribbling Steps
defstep
8.10

1 RRLL: Actor Basic

Dominik Pantůček <dominik.pantucek@trustica.cz>

Racket Rogue-Like Library: Actor Basic

Single-value flow-oriented language differentiating between flow steps and value expressions.

1.1 Introduction

Actor Basic is a S-Expression language. The basic element of the Actor Basic language is a S-Expression list with abasic-lambda as its first element and it forms an Actor Basic program. The Racket representation of such program is then a procedure which accepts a single argument (the actor), passes it through the steps of the program which may modify it and eventually returns the final actor as updated by all the steps performed.

The Actor Basic Actor must implement to gen:abasic-actor interface.

To create modules with Actor Basic programs, use the s-exp meta-language:

#lang s-exp rrll/abasic
 
...

For the structure of Actor Basic programs, see the rrll/abasic/expander.

When used as a standalone language, the define is an alias for define/provide-abasic from rrll/abasic/define.

1.2 Actor Basic Message

 (require rrll/abasic/message) package: rrll-abasic

This module provides a simple struct for sending messages to actors with optional data and queue for sending replies back.

struct

(struct amsg (code data queue))

  code : symbol?
  data : any/c
  queue : (or/c #f tiqueue?)
The underlying data structure used for messaging. Must be constructed using make-amsg and only amsg-code, amsg-data and amsg-queue are provided for accessing its fields.

procedure

(make-amsg code [#:queue queue #:data data])  amsg?

  code : symbol?
  queue : (or/c #f tiqueue?) = #f
  data : any/c = #f
A simple constructor for amsg? with optional keyword arguments for optional fields.

1.3 Actor Basic Generics

 (require rrll/abasic/generic) package: rrll-abasic

This module provides a generic interface that must be implemented by any struct used in actor basic procedures.

value

gen:abasic-actor : any/c

Generic interface that requires following procedures to be implemented:

procedure

(abasic-actor-send! actor v)  void?

  actor : abasic-actor?
  v : any/c
Must send the message to given actor.

procedure

(abasic-actor-recv! actor)  any/c

  actor : abasic-actor?
Must receive a message for given actor.

1.4 Actor Basic Steps

 (require rrll/abasic/steps) package: rrll-abasic

The steps represent actions that the actor can perform. The step specification is always a procedure that may accept optional arguments (if defined) and the result is a procedure that is applied to current actor state and returns a new actor state.

value

abasic-step? : contract?

 = (-> any/c ... (-> abasic-actor? abasic-actor?))
Not an actual contract?, but used in this documentation for reference.

step

(reply code [data])  abasic-step?

  code : symbol?
  data : any/c = #f
Implemented internally, not as a regular step. Should be used only within recv expression. Sends reply to optional reply channel of the message received by the enclosing recv expression.

Performs nothing if no such channel is bound.

The actor state is returned unchanged.

step

(debug fmt arg ...)  abasic-step?

  fmt : string?
  arg : any/c
Uses log-debug to send a message to current-logger at the 'debug level and returns the actor unchanged.

1.5 Actor Basic Expander

 (require rrll/abasic/expander) package: rrll-abasic

The expander compiles Actor Basic down to simple Racket forms that can be efficiently compiled.

For compatibility reasons the expander re-exports bindings of certain forms from racket/base and exports bindings of the new forms it introduces. It is an error to use any of these in improper context.

syntax

cond

syntax

else

syntax

case

syntax

and

syntax

or

syntax

when

This and all the previous bindings are re-exported from racket/base to ensure they are recognized as equal (as in free-identifier=?) in the Actor Basic syntax forms. Their usage outside of Active Basic programs (or in the wrong place therein) is an error.

procedure

(not v)  boolean?

  v : any/c
This procedure is reexported from racket/base for convenience.

syntax

loop

syntax

recv

syntax

recv-case

syntax

reply

syntax

with-recv

This and all the aforementioned bindings are exported for the same reasons as the previous group. Their improper usage is an error.

syntax

(abasic-lambda maybe-formal-arg steps ...)

Compiles Actor Basic program and returns the resulting procedure.

Each Actor Basic program has the following structure:

 

program

 ::= 

( abasic-lambda ( ) steps )

 

  |  

( abasic-lambda racket-expr steps )

 

steps

 ::= 

 

  |  

step steps

 

  |  

form steps

 

racket-expr

 ::= 

any Racket expression that evaluates to single value

When the second form of the abasic-lambda is used, the value given as formal parameter is actually substituted for default value when the program is run with no input value. It defaults to #f.

Emacs note: For reasonable indentation of the abasic-lambda expressions, put the following into your ~/.emacs file.

(put 'abasic-lambda 'racket-indent-function 1)

Assumes racket-(xp-)mode.

The steps may be primitive steps or special forms:

 

step

 ::= 

s-id

 

  |  

( s-id args )

 

form

 ::= 

loop

 

  |  

case

 

  |  

cond

 

  |  

recv

 

  |  

recv-case

 

  |  

recv-case*

 

  |  

with-recv

 

  |  

with-recv-case

 

s-id

 ::= 

bound identifier to abasic-step?

 

args

 ::= 

 

  |  

expression args

 

  |  

#:keyword expression args

 

#:keyword

 ::= 

any keyword?

The loop form is used to expres tail-recursion loops like with named let.

 

loop

 ::= 

( loop steps )

The case form compares given value - which may be obtained by arbitrary Actor Basic expression to either single constant tokens (primitive Racket values) or a list of such using eq? and the first one that matches chooses different branch of steps for continuing the evaluation of the program.

If none matches, there may be a default else branch as the last case. If none matches and there is no default branch, the result of evaluating the case is equivalent to no-operation and the current actor is passed through as-is. There can be no further case branches after the default else branch.

 

case

 ::= 

( case expression cases )

 

cases

 ::= 

 

  |  

( single-case ) cases

 

single-case

 ::= 

( value steps )

 

  |  

( ( values ) steps )

 

  |  

( else steps )

 

value

 ::= 

primitive value: boolean?, char?, number?, string?

 

values

 ::= 

 

  |  

value values

The cond form checks all the conditions in sequential order until it one evaluates to non-#f value and that branch of steps is chosen for further evaluation of the program. The conditions are Actor Basic expressions.

If none matches and there is a else branch, it is chosen. Without else branch the whole form is essentially no-operation and passes the current actor through as-is.

 

cond

 ::= 

( cond conds )

 

conds

 ::= 

 

  |  

single-cond

 

  |  

conds

 

single-cond

 ::= 

( expression steps )

 

  |  

( else steps )

Receiving messages by the Actor Basic program is possible if the given actor implements the gen:abasic-actor interface through the recv form or one of its derived forms. The recv form can bind:

 

recv

 ::= 

( recv code steps )

 

  |  

( recv ( code ) steps )

 

  |  

( recv ( code data ) steps )

 

  |  

( recv ( code data queue ) steps )

 

code

 ::= 

identifier, to which the message code symbol? will be bound

 

data

 ::= 

identifier to which the additional data will be bound

 

queue

 ::= 

identifier to which the queue for sending replies will be bound

Combination of recv and case forms - a recv-case form - is provided to allow simple message dispatching when no further processing is needed. The value which is tested agains the various cases is the message code received by recv.

 

recv-case

 ::= 

( recv-case cases )

The recv-case form is internally expanded as follows:

(recv-case
  (1 (debug "received 1"))
  (else (debug "received something else")))

The previous form becomes:

(recv msg
  (case msg
    (1 (debug "received 1"))
    (else (debug "received something else"))))

If easy message dispatching is desired in addition to binding the message contents, the recv-case* form can be used:

 

recv-case*

 ::= 

( recv-case* code cases )

 

  |  

( recv-case* ( code ) cases )

 

  |  

( recv-case* ( code data ) cases )

 

  |  

( recv-case* ( code data queue ) cases )

The expansion of recv-case* is similar to racket-case.

 

with-recv

 ::= 

( with-recv ( ( cc ) steps ) steps )

 

cc

 ::= 

identifier, to which the message code symbol? will be bound

 

maybe-recv-args

 ::= 

code

 

  |  

code data

 

  |  

code data sender

The with-recv form installs an asynchronous message handler in which the cc is bound to the continuation that - when evaluated as a step of the flow - resumes flow evaluation after the message interrupt was received. If it is not used and the handler steps reach the end of the form, evaluation of the flow continues right after the with-recv form.

As the with-recv form does not actually recv the message, a convenience form is provided:

 

with-recv-case

 ::= 

( with-recv-case ( ( cc maybe-recv-args ) cases ) steps )

The helper form is roughly expanded as follows.

(with-recv-case ((cc msg data sender) cases ...)
  steps ...)

After expansion it becomes:

(with-recv ((cc)
            (recv-case* (msg data sender)
              cases ...))
  steps ...)

Unlike with-recv the with-recv-case immediately receives the message using recv and therefore although the with-recv handler behaves like level-triggered interrupt, the interrupt condition is immediately handled and the handling will not happen again for the same message.

Actor Basic expressions can be used as step arguments, predicates in cond form, value in case form and arguments of arbitrary procedures within the expressions themselves.

 

expression

 ::= 

( quote racket-expr )

 

  |  

( and expressions )

 

  |  

( or expressions )

 

  |  

( p-id args )

 

  |  

p1-id

 

  |  

value

 

expressions

 ::= 

 

  |  

expression expressions

 

p-id

 ::= 

bound identifier to a procedure

 

p1-id

 ::= 

bound identifier to a procedure accepting one argument

The quote expression treats its contents like Racket’s quote does.

The and expression evaluates the expressions it contains one by one until it reaches either the end of the list or an expression that evaluates to #f either of which it returns. Without any expressions, sole (and) returns #t.

The or expression evaluates the expressions it contains one by one until it reaches either the end of the list or an expression that evaluates to non-#f which it returns. In the former case it returns #f. Without any expressions, sole (or) returns #f.

When the first argument of a list expression (multiple expressions enclosed in parentheses) is bound to a procedure, it is applied and before the application all its argument values are treated as Actor Basic expressions.

In the procedure application, it is not possible to evaluate complex expression in the first position of the list (the procedure to be applied must be an identifier, not an expression).

If a single identifier is to be evaluated and it is bound to a procedure that accepts exactly one argument, it is applied to the current actor and the result of the application is the result of this identifier evaluation. The current actor is not altered in such case - it can only be updated by steps through which the data structure flows.

Otherwise a single identifier or simple value is treated as-is and it is the result of such primitive expression.

1.6 Actor Basic Flow Graphs

 (require rrll/abasic/graph) package: rrll-abasic

This module provides alternative compilation of abasic-lambda to dot language - the graphviz source file.

syntax

(abasic-graph-print () step ...)

(abasic-graph-print default step ...)
Returns a string representing the flow graph in a dot format. This source does NOT contain the surrounding "digraph G{ ... }". That is to be added by appropriate define/provide/graph-abasic form.

The initial value is added as the starting node of the flow.

All the steps of the program are then added as follows:

1.7 Actor Basic Define

 (require rrll/abasic/define) package: rrll-abasic

This module sprinkles some syntactic sugar on top of the expander to allow simple usage when Actor Basic is used as standalone language.

syntax

(define-abasic (name) step ...)

(define-abasic (name value) step ...)
(define-abasic ((name arg ...)) step ...)
(define-abasic ((name arg ...) value) step ...)
The first form binds the name identifier to basic-lambda result without the default value for its argument specified. The default value is then #f.

The second form does the same while specifying alternative default value to the program argument.

The third - curried - form creates a procedure that returns the compiled program when evaluated.

The fourth - also curried - form creates a procedure accepting some arguments that creates a procedure that returns the compiled program when evaluated with actual argument values.

syntax

(define/provide-abasic (name) step ...)

(define/provide-abasic (name value) step ...)
(define/provide-abasic ((name arg ...)) step ...)
(define/provide-abasic ((name arg ...) value) step ...)
Firstly defines the program using define-abasic, secondly provides the binding it has just created and thirdly creates a binding of the same name in submodule abasic-graphs of the enclosing module that contains information that can be used by the Actor Basic graph extractor to produce a visual reprezentation of the program flow.

1.8 Scribbling Steps

 (require rrll/abasic/scribble) package: rrll-abasic

This module can be used to easily write scribblings of Actor Basic Steps.

syntax

(defstep prototype pre-flow ...)

Wrapper around defproc that sets its #:kind to "step" and ensures the return type is abasic-step?.