chialisp 21 - a more modern chialisp compiler
code

Why

The chialisp language is used to create spend conditions on the chia blockchain. It was developed with a strong connection to the platform it executes on, clvm. Clvm is a very good computation layer for its purpose; using infinite precision arithmetic means that a program can have no overflow conditions that it doesn't explicitly opt into, and its structure as an environment and an expression of only numbers and pairs makes formal analysis of computations in it relatively easy to undertake. As such, a more human friendly programming language exists to be compiled to clvm, supporting names and numbers in a more familiar style.

The chialisp compiler written by Richard Kiss has filled this role, but has some drawbacks. It is written in a style that highly couples it to the underlying representation of clvm which limits one's ability to provide user facing enhancements to the chialisp language itself, and it uses its optimizer to actually run compilation, so optimization and compilation aren't separable. Given that, it's difficult to add new features and be certain code generation won't be disturbed in unexpected ways. Adding to that the need to keep the base programs in chia compiling in exactly the same way and that's why I wrote a new compiler for chialisp that to the extent possible is focused on diagnostics, maintainability and extensibility for future chialisp programs written by users of the chia blockchain.

Since it's very new, I'm asking chialisp users and those curious to try it to give feedback.

Quickstart

Installing

(venv) $ git clone https://github.com/prozacchiwawa/clvm_tools_rs
(venv) $ cd clvm_tools_rs
(venv) $ cargo build

optional - enable use in python code based on chia-blockchain

(venv) $ maturin develop --release
(venv) $ export CLVM_TOOLS_RS=true

shell commands - installing

An example program (from Custom Puzzle Lock)

(mod (password new_puzhash amount)

                                    
     (include *standard-cl-21*) ;; Specify chialisp-21 compilation.

                                    
     (defconstant CREATE_COIN 51)

                                    
     (if (= (sha256 password)
          (q . 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824))
       (list (list CREATE_COIN new_puzhash amount))
       (x)
       )
     )

test.cl

Build and run it

(venv) $ ./target/debug/run test.cl > test.clvm
(venv) $ brun test.clvm '(hello 0x5f5767744f91c1c326d927a63d9b34fa7035c10e3eb838c44e3afe127c1b7675 2)'
((51 0x5f5767744f91c1c326d927a63d9b34fa7035c10e3eb838c44e3afe127c1b7675 2))

shell commands - compiling and running

Features

Better diagnostic information

(venv) $ ./target/debug/run test.cl
test.cl(4):14-test.cl(4):20: no such callable 'sha257'

error reporting

Scoping to reduce cognitive load

You don't need to spend time tracking down collisions between names you define and the clvm primitives. Arguments to defined functions and macros operate distinctly from arguments to the program as a whole.
(mod (a)
     (include *standard-cl-21*)
     (defun f (a) (+ a 1))
     (f (* a 2))
     )

scope.cl

Let forms

(mod (password new_puzhash amount)

                                    
     (include *standard-cl-21*) ;; Specify chialisp-21 compilation.

                                    
     (defconstant CREATE_COIN 51)

                                    
     (defun check-password (password)
       (let ((password-hash (sha256 password))
             (real-hash 0x2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824))

                                    
         (= password-hash real-hash)
         )
       )

                                    
     (if (check-password password)
         (list (list CREATE_COIN new_puzhash amount))
       (x)
       )
     )

test.cl with let form

cldb trace

(venv) $ ./target/debug/cldb test.cl '(idk 0x5f5767744f91c1c326d927a63d9b34fa7035c10e3eb838c44e3afe127c1b7675 2)'
...
- 'Arguments': '()'
  'Env': '(2 (1 9 (11 5) (1 . 20329878786436204988385760252021328656300425018755239228739303522659023427620)) 1)'
  'Env-Args': '(idk 43124150325653191095732712509762329830013206679743532022320461771503765780085 2)'
  'Failure': 'clvm raise in (8) (())'
  'Failure-Location': 'testws.cl(13):9'
  'Function': 'x'
  'Operator': '8'
  'Operator-Location': 'testws.cl(13):9'

test.cl with wrong password: the site raising the exception is shown

Use checking

$ ./target/debug/run --check-unused-args \
  '(mod (arg) (include *standard-cl-21*) (defun f (a b) (+ (* a a) a)) (f 3 arg))'
unused arguments detected at the mod level (lower case arguments are considered uncurried by convention)
 - arg
$ ./target/debug/run --check-unused-args \
  '(mod (arg) (include *standard-cl-21*) (defun f (a b) (+ (* b b) a)) (f 3 arg))'
(2 (1 2 2 (4 2 (4 (1 . 3) (4 5 ())))) (4 (1 2 (1 16 (18 11 11) 5) 1) 1))

Unused live argument was caught by use checker

Since upper case arguments have been used to indicate arguments that are present when the puzzle hash is computed, chialisp can check whether lower case arguments to the program play a role in the output. An argument that doesn't affect the output can signal a likely error. You can mark arguments intentionally unused by starting with underscore.

Syntax

Module

(mod args [helper-forms...] body)

A module defines its arguments and gives its definitions. This is what can be thought of as a 'chialisp program'.

Helper forms

(include file)

Including external definitions ( file contains a single list form ([helper-forms...]) )

(defconstant name value)

A constant defined in a module

(defmacro name args [helper-forms...] body)

A macro is given the argument forms provided pre-compilation and executed within the compiler.

(defun[-inline] name args body)

A function which is stored in the environment until used. Can be called recursively if not inlined.

Arguments support haskell style destructuring capture.

> (defun leftof (@ point (x y)) (if x (list (- x 1) y) point))
> (leftof 3 5)
< (2 5)
> (leftof 0 5)
< (0 5)
> data Pt = Pt Int Int deriving Show
> leftof p@(Pt x y) = if x /= 0 then Pt (x - 1) y else p
> leftof $ Pt 3 5
Pt 2 5
> leftof $ Pt 0 5
Pt 0 5

Body forms

(callable [expressions...])

Callables are defined by helperforms and predefined in the environment to match the canonical clvm operators. Macros replace the call site with the macro result.

(let ([bindings]) body)

With bindings given like (name expression), the let binding introduces new local names in a scope.

(let* ([bindings]) body)

Recursive let bindings ala traditional lisp. Each binding becomes available to the ones that follow.

(com [source])

A special form used in macros that recursively invokes the compiler on the given code. Because macros evaluate to runnable clvm, the user's code must be compiled by the macro to be used (for example) by the 'a' apply operator in clvm.

@

References the full environment.

(@ number)

A special form allowing a number to be used as an environment reference in clvm style.

Predefined macros

(list ...)

Constructs a list of the given argument values using clvm primitives to create the cons pairs.

(if cond if-true if-false)

As the clvm primitive i is eager, produce a form that invokes either the code in if-true or the code in if-false based on the condition using the 'a' apply primitive in clvm.

Classic chialisp defines if as a macro, and that is preserved in this version of the compiler.