Skip to content

jingtaozf/literate-lisp

Repository files navigation

http://quickdocs.org/badge/literate-lisp.svg https://github.com/jingtaozf/literate-lisp/workflows/Continous%20Integration/badge.svg

Table of Contents

Introduction

literate-lisp provides an easy way to use literal programming in Common Lisp language. It extends the Common Lisp reader syntax so a Common Lisp vendor can read Org files as Common Lisp source files.

By using this package (literate-lisp), Emacs Org mode, and Emacs Lisp library polymode, literate programming can be very easy, with one Org file containing both documentation and source code, and this Org file can interact well with SLIME.

The implementation detail of literate-lisp is in file ./literate-lisp.org (pdf version).

This library contains the following files:

Tutorial

The restriction to the Org file

The Org file should start with a comment character and a space character(“# “), to drive lisp reader into Org syntax. Actually it can be a convenient way for us to specify some local variables, for example I often put them in the first line of an Org file:

# -*- encoding:utf-8 Mode: POLY-ORG;  -*- ---

Which make Emacs open file with utf-8 encoding and poly-org-mode.

Installing polymode in Emacs

It’s better to edit the Org file with polymode, which will make code block use its native file mode. The following Emacs Lisp scripts in .emacs will install it.

(use-package poly-org
    :ensure t)

How to insert code block quickly

Please have a look of the section How to insert code block in Org file in library literate-elisp.

Please note that literate-lisp now support parsing Org property values (property syntax), so there is no need to insert :load no in header argument now, you can set them as Org properties.

To disable insertion of unnecessary header argument, you can set Org property literate-insert-header to no.

The load code block header argument

Please have a look of the section new defined header argument load in ./literate-lisp.org.

Reference named blocks as global parameters

If a block has a named for it, that is, with a #+NAME: before it like this:

#+NAME: js-demo-code
#+BEGIN_SRC js
  document.getElementById("demo").innerHTML = "Hello JavaScript";
#+END_SRC

Then after loading, a global parameter js-demo-code will contain the string in above block.

It is more friendly than write this string in lisp directly, because org-mode can provide syntax for it and poly-mode can even enable us edit this code block in js-mode.

You can visit these named blocks by Emacs Lisp function org-babel-goto-named-src-block, or by hacking sly like this:

(defun sly-edit-definition-of-named-block (&optional name method)
  (when (string-prefix-p "#+END_" (string-trim (buffer-substring (line-beginning-position) (line-end-position))) t)
    (let ((case-fold-search t))
      (search-forward-regexp (format "#\\+NAME:\s+%s" name))
      (forward-line 2)
      (goto-char (line-beginning-position)))))
(eval-after-load "sly"
  '(advice-add 'sly-edit-definition :after #'sly-edit-definition-of-named-block))

You can hack slime the same way.

NOTE You have to install literate-lisp to active *readtable* in sly when using SBCL to make above patch work, because the SBCL backend in sly will read the source code inside file when find definitions.

(literate-lisp:install-globally)

How to debug Org file in LispWorks IDE

You have to add the following code in your .lispworks to enable the debug facility in Lispworks Editor.

(defun check-org-mode (buffer truename)
  (when (and truename (equal (pathname-type truename) "org"))
    (setf (editor:buffer-major-mode buffer) "Lisp")))
(editor:add-global-hook editor::read-file-hook 'check-org-mode)

Thanks for Martin Simmons in LispWorks to support the above configuration code.

How to integrate with namded-readtables

You may find that named-readtables is friendly to define the syntax for literate-lisp in your code like this:

(named-readtables:defreadtable literate-lisp
  (:merge :standard)
  (:dispatch-macro-char #\# #\space #'literate-lisp::sharp-space)
  (:dispatch-macro-char #\# #\+ #'literate-lisp::sharp-plus))

How to write user initialization file with literate programming style

You can put all initialization code in an Org source file, all you need is to load literate-lisp firstly. For example, you can put the following code in file ~$HOME/.sbclrc~ for SBCL.

(require :asdf)
#-quicklisp
(let ((quicklisp-init "~/quicklisp/setup.lisp")
      (quicklisp-install "~/quicklisp.lisp"))
  (cond ((probe-file quicklisp-init)
         (format *terminal-io* "loading quicklisp...~%")
         (load quicklisp-init)
         (format *terminal-io* "loading quicklisp...done~%"))
        ((probe-file quicklisp-install)
         (load quicklisp-install)
         (funcall (intern "INSTALL" :quicklisp-quickstart)))))

(load "~/projects/common-lisp/literate-lisp/literate-lisp.asd")
(ql:quickload :literate-lisp)
(literate-lisp:with-literate-syntax
  (load "~/projects/common-lisp/config/init-lisp.org"))

I find it useful for various Lisp vendors so all initialization code for them can be in just one file.

How to include Org code with ASDF package-inferred-system extension

The ASDF package-inferred-system extension is wonderful, in which each file is its own system, and dependencies are deduced from the defpackage form or its variant, uiop:define-package. You can also use literate-lisp to make a package inferred system by writing an ASD definition like this:

(asdf:defsystem literate-libraries
  :serial t
  :defsystem-depends-on (:literate-lisp)
  :default-component-class :org
  :class :package-inferred-system)

Here *:class :package-inferred-system* enables the package-inferred-system extension, and *:default-component-class :org* means that ASDF will look for all Org files to find out a system and load it.

For example, you can create an Org file in the same directory of above ASD definition file named as utilities.org and contains the following code

# -*- encoding:utf-8 Mode: POLY-ORG;  -*- ---
* Create a package for this package inferred system
#+BEGIN_SRC lisp
(defpackage literate-libraries/utilities
  (:use :cl)
  (:import-from :flexi-streams :octet :make-flexi-stream)
  (:import-from :log4cl :log-config)
  (:documentation "a utility module."))
#+END_SRC
* implementation
... ...

After loading the above ASD definition file, you can load system literate-libraries/utilities in your REPL.

(load "/some/path/literate-libraries.asd")
(ql:quickload :literate-libraries/utilities)

Please upgrade to ASDF 3.3.4.5 or later, it is not supported in earlier ASDF versions.

How to tangle to a bundle of lisp files from one Org file

Yes, now you can tangle one Org file to a bundle of lisp files, so to share it to team members with more clear interface.

Please have a look of tangle to multiple files for one Org file or the usage of Org property LITERATE_EXPORT_PACKAGE and LITERATE_EXPORT_NAME in file ./literate-lisp.org.

Packages written by literate-lisp

  • s-graphviz an S-expression presentation of GraphViz DOT Language

A demo literate application

The ASD file

We use the original ASD definition file, and extend the ASDF syntax(The documentation of extended ASDF syntax can be found in literate-lisp.org).

In a short word, we should load literate-lisp by ASDF keyword :defsystem-depends-on and declare the Org source file with new ASDF keyword :org.

Now let’s define the ASDF system file ./literate-demo.asd for this demo package

(asdf:defsystem literate-demo
  :author "Xu Jingtao <[email protected]>"
  :version "0.1"
  :licence "MIT"
  :serial t
  :description "a demo project of literate-lisp"
  :defsystem-depends-on ("literate-lisp")
  :depends-on (:iterate #+dev :clgplot)
  :components ((:module :demo :pathname "./"
                        :components ((:org "puzzle")
                                     (:org "readme"))))
  :properties ((version "0.1")))

Which will load ./puzzle.org and this file directly as a lisp source file.

The whole content of ASDF definition file is in ./literate-demo.asd.

A demo package for this file

(defpackage :literate-demo
  (:use :cl)
  (:export ))
(in-package :literate-demo)

Test cases

Preparation

The FiveAM library is used to test.

(eval-when (:compile-toplevel :load-toplevel :execute)
  (unless (find-package :fiveam)
    #+quicklisp (ql:quickload :fiveam)
    #-quicklisp (asdf:load-system :fiveam)))
(5am:def-suite literate-demo-suite :description "The test suite of literate-demo.")
(5am:in-suite literate-demo-suite)

test case for named block

Let’s define a named code block for some javascript code:

{
    console.log("Hello");
}

Then try to read it in our test case

(5am:test named-block
  (5am:is (stringp js-demo-code-1))
  (5am:is (not (null (position #\" js-demo-code-1 :test #'char=)))))

run all tests in this library

This function is the entry point to run all tests and return true if all test cases pass.

(defun run-test ()
  (5am:run! 'literate-demo-suite))