Rholang interpreter overview
Phases
- Parsing
- Normalization
- Reduction
Parsing
Parsing of the program consists of two phases: lexing and parsing. Lexer takes the input stream and slices it into tokens ("meaningful words"). Parser reads a string of tokens and produces a syntax tree. For lexer we use JFlex. Parsing is carried out by CUP. The outputs are java classes representing the grammar. Grammar of rholang is defined using BNFC.
Normalization
Input to the normalization phase is the output from the parsing phase; it's an AST representing the rholang program. Normalizer produces terms where processes in parallel have been binned according to their type (sends, receives, expressions).
Another necessary step in the normalization phase is sorting. Because rholang's |
is commutative, to normalize properly we need to sort terms. Sorting happens just after normalization (if it was done before, normalization would need to preserve ordering) and after substitution.
Reduction
Consists of two phases: substitution and evaluation. We go through the program recursively, substituting and then evaluating the terms. Substitution takes the term and replaces bound variables in it with their actual values from the environment. During evaluation terms are reduced. It's best to illustrate with an example:
Assuming there is an x=5 in the environment, rholang program@{10+5+x | @x!(5)}!(Nil)
goes through the following steps:
- Evaluate channel of the send --
{10+5+x | @x!(5)}
- Substitute:
{10 + 5 + 5 | @5!(5)}
- Reduce:
{20|@5!(5)}
- Substitute:
- Evaluate body of the send (no evaluation required)
- Evaluate send
- Place the send in the RSpace
- Place the send in the RSpace
The spatial matching algorithm deserves a special mention here. It is used for matching targets with patterns. Spatial matching is performed in
for (<name pattern> <- <channel>) { <process> }
match <process> { <process pattern> => <process> }
<process> matches <process pattern>
For more information of patterns and spatial matching algorithms please consult Rholang tutorial.
Interaction with RSpace
"RSpace is the beating heart of RChain" -- Anonymous
The interaction between rholang's send
-s and receive
-s happens with the help of RSpace. When a Send
is evaluated, RSpace checks whether there is a matching continuation waiting on the Send
-s channel in the tuplespace. If there's no continuation on the channel, it stores the Send
in the tuplespace. If there is, it validates whether data matches the pattern guard of the continuation. If the match is successful then the continuation is executed. Similarly, when evaluating a Receive
, we search for a matching Send
in the tuplespace.
Examples
1. Send
then Receive
- Start with empty tuplespace.
- Evaluate
send
:@0!(1)
.- no receive reading from
@0
channel in the tuplespace - store thesend
.
- no receive reading from
- Tuplespace state:
@0!(1)
. - Evaluate
receive
:for(x <- @0) { @1!(x) }
:- There is a
send
on@0
channel in the tuplespace, retrieve it. - Validate that the pattern guard of the continuation matches data being sent on the channel. Since our pattern is a free variable (
x
) it will match anything. - Substitute occurrences of
x
in the continuation with the data received and execute. This will result in sending 1 over the@1
channel.
- There is a
- Tuplespace state:
@1(1)
.
2. Receive
then Send
- Start with empty tuplespace.
- Evaluate
Receive
:for(@{Nil /\ x} <- @0) { @x!(10) }
.- no
send
on@0
in the tuplespace - store thereceiv
e.
- no
- Tuplespace state:
for(@{Nil /\ x} <- @0) { @x!(10) }
. - Evaluate
Send
:@0!(10)
.- There is a
receive
listening on @0 but it's waiting for a@Nil
being sent, so it doesn't match the data we send (10). - Store new
send
in the tuplespace.
- There is a
- Tuplespace state:
for(@{Nil /\ x} <- @0) { @x!(10) } | @0!(10)
. - Evaluate
Send
:@0!(Nil)
.send
matches thereceive
in the tuplespace, retrieve it.- Bind data sent over from channel
@0
to `x`
and substitute all occurrences ofx
in the body of the continuation with bound data - Evaluate body
@Nil!(10)
- Tuplespace state:
@Nil!(10) | @0!(10)
Unforgeable names
Unforgeable names are very much like object references in languages like Python or Java. Internally, they act like pointers, but there's no way in the language to supply an integer and get a reference to the object living at that address. An unforgeable name is implemented internally as a 256-bit identifier, and that identifier is visible on the blockchain, but there is no language production that takes 256 bits and returns the corresponding name. Unforgeable names are created with the new x in {...}
production. The only way to get hold of an unforgeable name is to be in the lexical scope of that name or to receive it as part of a message. The algorithm used for generating the 256-bit random numbers is based on Blake2b512.
Cost Accounting
Every Rholang program has a cost associated with running it. Rholang programs are charged:
- when performing a substitution. Cost is proportional to the binary size of the data after substitution
- when performing a spatial match - overall cost is an accumulated cost of all equality checks performed on all patterns against the data being matched. This is also the case when evaluating a
consume
(orproduce)
since this will perform a spatial match in search for matchingproduce (or
which results inconsume
)COMM
event. - when stored in the Tuplespace - cost is proportional to the binary size of the data being stored.