~freakingpenguin/guile-blocks

An Org Mode inspired implementation of source blocks in Guile Scheme.
* README.org (Introduction): Add forward slashes to hyperlink.
* README.org: Mention website manual.
* .build.yml: Fix issue in website tarball.

clone

read-only
https://git.sr.ht/~freakingpenguin/guile-blocks
read/write
git@git.sr.ht:~freakingpenguin/guile-blocks

You can also use your local clone with git send-email.

Table of Contents

builds.sr.ht status

1. Introduction

This repository contains guile-blocks, an Org Mode inspired implementation of source blocks in Guile Scheme.

This file provides an overview of Guile Blocks. It does not attempt to detail every piece of functionality. For a more thorough review, consult the manual. The manual is hosted at https://guile-blocks.info.

2. Building

This project can be built as a Guix package. To do so, run

guix build -f guix.scm

Alternatively, to build in the worktree, run

guix shell -CDf guix.scm # optional, set up dependencies
./bootstrap
./configure
make
make check               # optional, run tests

The dependencies for this project are listed in the guix.scm file.

This repository is available as a Guix channel. A channels.scm file is in the repository root.

3. Documentation

3.1. Blocks

Blocks can be created using the make-block* function.

(define ruby-hello-world
  (make-block* #:code "print 42"
               #:language ruby))

A define-block macro is provided as a shorthand.

(define-block ruby-hello-world
  #:code "print 42"
  #:language ruby)

Blocks are immutable. Functions which take and return a block are returning new blocks. Blocks can not be modified after they are created.

3.1.1. Running Blocks

Blocks are run using the run-block function.

(run-block ruby-hello-world)

run-block returns a new block, identical to the previous block but with the #:results field set. If the #:results field is already set to a truthy value, the evaluator is not run and the same block is returned.

It is possible to run a block at the same time it is created by passing #:run? #t to make-block* or define-block.

(block-result (make-block*
               #:code "print(10 + 9)"
               #:language ruby
               #:run? #t)) ;=> "19"

3.1.2. Combining Blocks

Multiple blocks can be combined into one new block using the combine-blocks function.

combine-blocks* is a wrapper around combine-blocks that automatically inserts its arguments into a list.

(define-block ruby-set-variables
  #:code "x = 400"
  #:language ruby)

(define-block ruby-print-x
  ;; Language may be omitted or the same.
  #:code "print x")

(block-result
 (run-block (combine-blocks*
             ruby-set-variables ruby-print-x))) ;=> "400"

Combining blocks takes the #:code field of each block and appends them together. The #:separator field of the parent is placed in between the two blocks, by default "\n".

Combining blocks also updates the #:subblocks field, which is discussed later.

3.1.3. Pipelines

Blocks can be run as a pipeline. The results of the parent block is inserted into the child's #:code field, using the #:chain string as a marker for what to replace.

Pipelines are not limited to two elements.

run-blocks* is a wrapper around run-blocks that automatically inserts its arguments into a list.

(define-block ruby-print-python-code
  #:code     "puts \"print(74, end='')\""
  #:language ruby)

(define-block python-print
  #:code     "%ruby%"
  #:chain    "%ruby%"     ;text to replace in #:code
  #:language python)

(define-block ruby-add-1
  #:code     "print %python% + 1"
  #:chain    "%python%"   ;this can be anything
  #:language ruby)

(block-result (run-blocks*
               ruby-print-python-code
               python-print
               ruby-add-1)) ;=> "75"

If the child's #:code field is not set, the #:result field is inserted directly into the #:code field.

(define-block guile-print-guile-code
  #:code ''(+ 1 1)  ;result => '(+ 1 1)
  #:language guile-direct)

(define-block guile-eval
  #:language guile-direct)

(block-result (run-blocks* guile-print-guile-code guile-eval)) ;=> 2

It is possible to run a nested list of blocks directly, without needing to call combine-blocks first.

(run-blocks `((,a ,b)
              ,c ,d))
;; is equivalent to
(run-blocks `(,(combine-blocks* a b)
              ,c ,d))

Directly running nested blocks is only supported one level deep.

(run-blocks `((,a ,b (,c ,d))
              ,d)) ;invalid
(run-blocks `((,a ,b ,(combine-blocks* c d))
              ,d)) ;valid

Explicit calls to combine-blocks would be required.

3.1.4. Subblocks

When combining and pipelining blocks, a special field called #:subblocks is updated. This field indicates how the final block was built.

When combining blocks, the subblocks are set as a list. For example,

(block-subblocks
 (combine-blocks* a b)) ;=> `(,a ,b)

Combining more blocks will append to the list.

(block-subblocks
 (combine-blocks* (combine-blocks* a b) c)) ;=> `(,a ,b ,c)

When running a pipeline, the subblocks of each pipeline element are inserted into a list. Elements without subblocks are inserted directly.

(block-subblocks (run-blocks*
                  (combine-blocks* a b)
                  c d)) ;=> `((,a ,b) ,c ,d)

A side effect of this is it is impossible to tell by looking at the #:subblocks field alone whether a block created by running a pipeline or by combining blocks. A block created by running a pipeline of blocks that were not previously combined would look identical to a block created by combining blocks.

The elements of the #:subblocks field are the original blocks. When running a pipeline, the blocks are not executed directly. Instead, the #:code field is updated by replacing #:chain, and that new block is the one that executes. However, that is not reflected in the subblocks. The subblocks #:code field will not be replaced.

4. Credits

  • The project structure came from Erik Edrosa.
  • The Automake test-driver Scheme implementation came from GNU Guix.