Home

Awesome

æREPL --- an interactive shell for Sophia

Try Sophia, test your contracts, check "what if", calculate a factorial. Completely offline, independently from remote networks, no dockers required.

If you are not familiar with Sophia, check its documentation first.

Setup

Dependencies

Tools:

Libraries:

Local build

Clone the repo:

git clone https://github.com/aeternity/aerepl.git

Set up the environment:

export ERLANG_ROCKSDB_OPTS="-DWITH_SYSTEM_ROCKSDB=ON -DWITH_LZ4=ON -DWITH_SNAPPY=ON -DWITH_BZ2=ON -DWITH_ZSTD=ON"
export CXXFLAGS="-Wno-error=shadow -Wno-deprecated-copy -Wno-redundant-move -Wno-pessimizing-move"

Build the project (the make step may need to be executed twice, see this issue):

cd aerepl
make

Launch the REPL

./aerepl

Docker/podman image

For a consistent setup, a docker image can be created:

make docker

Then to start the dockerized REPL:

docker run -i aeternity/aerepl:local

Use make podman for a podman build.

CLI usage

æREPL usage patterns are highly inspired by GHCi. Most of the syntax is a direct adaptation of it to the needs of Sophia and FATE. This section shall provide an overview of the most fundamental features and typical workflows. If you find navigation clunky, please consider using rlwrap.

Main functionality of æREPL is to evaluate Sophia expressions:

AESO> 2 + 2
4

Aside from that, values can be assigned to REPL-local variables (pattern matching is supported)

AESO> let x = 2
AESO> let (q, w) = (x + 1, x - 1)
AESO> q
3
AESO> w
1

Functions and types are supported as well

AESO> record point = {x : int, y : int}
AESO> function get_x(p : point) = p.x
AESO> get_x({x = 100, y = 42})
100

NOTE: in-REPL functions cannot use in-REPL variables and other functions yet.

Typechecking

A common query is to ask about the type of an expression. This is done using the :type command:

AESO> :type Oracle.query
(oracle('a, 'b), 'a, int, Chain.ttl, Chain.ttl) => oracle_query('a, 'b)

Working with files

æREPL allows loading files to call their code and deploy contracts defined in them. To load a file, the :load command may be used:

// File: Test.aes
namespace N =
  function f() = 100

contract C =
  entrypoint f() = N.f() + 23
AESO> :load Test.aes
AESO> N.f()
100
AESO> let c = Chain.create() : C
AESO> c.f()
123

If multiple files are loaded, only the most recent one is brought to the scope. To include other loaded files into the working scope, Sophia's include should be used:

AESO> include "List.aes"

Note that :load reloads all listed files and unloads those skipped. Yet another useful command is :reload, which reloads all loaded files, preserving the included scope.

AESO> :load X.aes Y.aes
AESO> Y.y()  // defined in Y.aes
"y"
AESO> X.x()  // defined in X.aes
1:1 Unbound variable: X.x
AESO> include "X.aes"
AESO> X.x()  // Now it works
"x"

:load and :reload clear out the scope, meaning that they will clean

In-REPL state

æREPL maintains its own Sophia state which can be accessed using standard operations put and state, as well as stateful functions. By default, the state is set to () : unit. In order to change it to some other type, :state command should be used:

AESO> :set print_unit true
AESO> state
()
AESO> :state 1000
AESO> state + 1
1001
AESO> put(state / 2)
()
AESO> state
500

Note that :state is parameterized by value, not by type. The value has to have a fully instantiated, non-functional type. Changing the state using the :state commands clears out all user-defined variables and functions --- if that is not desired, put should be used to modify the state if possible.

Configuration

æREPL can be configured interactively using the :set command. The syntax for this command is :set OPTION [ARGS], for example

AESO> :set print_unit true

Currently the supported options are:

OptionArgumentsDescription
call_gasnon-neg intDetermines the amount of gas to be supplied to each query.
---------------------------------------------:--------------------------------------------------------------------
call_valuenon-neg intThe value, the amount of tokens supplied with the query.
---------------------------------------------:--------------------------------------------------------------------
call_gas_pricenon-neg intThe result of Call.gas_price
---------------------------------------------:--------------------------------------------------------------------
call_originpubkeyThe account to initiate execution of in-REPL code.
Defines Call.origin and initial Call.caller.
---------------------------------------------:--------------------------------------------------------------------
call_contract_creatorpubkeyThe result of Contract.creator.
---------------------------------------------:--------------------------------------------------------------------
call_feenon-neg intThe result of Call.fee.
---------------------------------------------:--------------------------------------------------------------------
call_heightnon-neg intThe result of Chain.block_height.
---------------------------------------------:--------------------------------------------------------------------
print_gastrue/falseIf true, REPL will print gas used for evaluating each expression.
---------------------------------------------:--------------------------------------------------------------------
print_formatsophia/fate/jsonDetermines the syntax used to display values.
---------------------------------------------:--------------------------------------------------------------------
print_unittrue/falseWhether to display unit (()).
---------------------------------------------:--------------------------------------------------------------------
print_typetrue/falseWhether to display the type after each eval.
---------------------------------------------:--------------------------------------------------------------------
loc_backwardsnon-neg intNumber of previous lines to be displayed when using location.
---------------------------------------------:--------------------------------------------------------------------
loc_forwardsnon-neg intNumber of further lines to be displayed when using location.

Output format

By default, the REPL shall display values preserving compatibility with Sophia syntax. An exception from that rule are values containing functions which in FATE are represented as pairs of function name and closure. In that case, the output will take a form of "<fun $FUNHASH>" : $TYPE where $TYPE is the type of the function (according to the Sophia code, not FATE bytecode), and $FUNHASH is a hex-encoded shortcut of the original function's name hashed with BLAKE2B (as it is stored in the bytecode). For example

AESO> () => ()
"<fun E3472E14>" : () => unit

If print_format is set to fate, the value shall be displayed as an Erlang term that describes the exact representation of the value in the runtime.

AESO> :set print_format fate
AESO> record r = {x : int, y : int}
AESO> {y = 100, x = 7}
{tuple,{7,100}}
AESO> Some(1)
{variant,[0,1],1,{1}}
AESO> () => ()
{tuple,{<<227,71,46,20>>,{tuple,{}}}}

If set to json, the values will be encoded in JSON format. Information about record fields and datatype constructor names shall not be dropped. Function values are represented as strings in the same manner as in the sophia format.

AESO> :set print_format json
AESO> record r = {x: int, y : int}
AESO> ({x = 3, y = 4}, "STRING")
[
  {
    "x": 3,
    "y": 4
  },
  "STRING"
]

Multiline input

To type in a multiline block of code, surround it with :{ and :}:

AESO> :{
| let x = 123
| x
| :}
123

Generic server interface

If REPL is used as a library for a different tool, its generic server can be used for more structured interface. The server is located in src/aere_gen_server.erl and implements the standard gen_server Erlang behaviour.

Startup

Use aere_gen_server:start/1 to start up the server. aere_gen_server:start/2 allows to start a server under a custom name. The argument is property list with the following fields:

Call

Cast

Functions

All above calls and casts are exposed as function calls for convenience. Additionally, the following are offered: