├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── flake.lock ├── flake.nix ├── papyrus.asd ├── src ├── papyrus.lisp └── reader.lisp └── test └── reader.lisp /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run Nix Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | lisp: [ecl, sbcl, abcl, ccl, mkcl, clisp, cmucl_binary, clasp-common-lisp] 17 | os: [ubuntu-24.04, ubuntu-24.04-arm, macos-15] 18 | exclude: 19 | - lisp: mkcl 20 | - os: macos-15 21 | lisp: ccl 22 | - os: macos-15 23 | lisp: mkcl 24 | - os: macos-15 25 | lisp: clasp-common-lisp 26 | - os: macos-15 27 | lisp: cmucl_binary 28 | - os: ubuntu-24.04-arm 29 | lisp: ccl 30 | - os: ubuntu-24.04-arm 31 | lisp: mkcl 32 | - os: ubuntu-24.04-arm 33 | lisp: clasp-common-lisp 34 | - os: ubuntu-24.04-arm 35 | lisp: cmucl_binary 36 | 37 | name: Test on ${{ matrix.os }} with ${{ matrix.lisp }} 38 | 39 | runs-on: ${{ matrix.os }} 40 | 41 | steps: 42 | - name: Checkout repository 43 | uses: actions/checkout@v4 44 | 45 | - name: Install Nix 46 | uses: cachix/install-nix-action@v30 47 | with: 48 | nix_path: nixpkgs=channel:nixos-unstable 49 | 50 | - name: Run Nix Test 51 | run: nix run .#test-${{ matrix.lisp }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.FASL 2 | *.fasl 3 | *.lisp-temp 4 | *.dfsl 5 | *.pfsl 6 | *.d64fsl 7 | *.p64fsl 8 | *.lx64fsl 9 | *.lx32fsl 10 | *.dx64fsl 11 | *.dx32fsl 12 | *.fx64fsl 13 | *.fx32fsl 14 | *.sx64fsl 15 | *.sx32fsl 16 | *.wx64fsl 17 | *.wx32fsl 18 | .qlot/ 19 | coverage/ 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019-2024 TANIGUCHI Masaya 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | (defpackage #:papyrus/README 3 | (:use #:cl)) 4 | (in-package #:papyrus/README) 5 | (named-readtables:in-readtable papyrus:md-syntax) 6 | 7 | # Papyrus 8 | 9 | A literate programming tool for Common Lisp + Markdown / Org mode / POD 10 | 11 | ## About This Project 12 | 13 | ### Philosophy 14 | 15 | *Papyrus* is the name of a programming style as well as the name of a tool 16 | with which to implement it. The author of *papyrus* developed it to do 17 | literate programming in LISP better than WEB, developed by Donald Knuth. 18 | WEB and it's derived softwares are used in various programming languages. 19 | They require developers compiling with them to obtain the source code. 20 | It is required in order to do literate programming in C and Pascal, but isn't 21 | in Common Lisp because Common Lisp has the reader macro which changes the 22 | source code when the system reads it. 23 | 24 | ```mermaid 25 | flowchart TB 26 | subgraph Knuth's system 27 | WEB -- CTANGLE --> Pascal 28 | WEB -- CWEAVE --> TeX 29 | end 30 | subgraph Our system 31 | Papyrus -- reader macro --> CommonLisp 32 | Papyrus --- Markdown 33 | end 34 | ``` 35 | 36 | *Papyrus* makes your markdown executable with the reader macro of Common Lisp. 37 | For example, the author wrote this document with *Papyrus*. You can execute it 38 | by running `ros run -l papyrus.asd -e '(require :papyrus)' -l README.md -q`. 39 | How about this? Let's make your project more beautiful and useful! 40 | 41 | ```lisp 42 | (princ "Hello, Papyrus!") 43 | ``` 44 | 45 | ### Copyright 46 | 47 | Copyright (c) 2019 -- 2024 TANIGUCHI Masaya All Rights Reserved 48 | 49 | ### License 50 | 51 | MIT. See the [license texts](./LICENSE). 52 | 53 | ### Precaution 54 | 55 | This is a new project. Please send me your feedback if you find any issues. 56 | 57 | - [Project home](https://github.com/tani/papyrus) 58 | 59 | ## Tutorials 60 | 61 | In *Papyrus*, you can write any text like the following, 62 | and make the file extension `md`. Also, you can write with Markdown, 63 | especially CommonMark whose specification can be found at 64 | [commonmark.org](https://commonmark.org). *Papyrus* only evaluates codeblocks 65 | *after* `(enable-md-syntax)` that are enclosed by ` ```lisp ` and ` ``` `. 66 | The indented codeblock *before* `(enable-md-syntax)` is important, as this codeblock 67 | specifies the required packages. Please do not forget it. 68 | 69 | (defpackage #:tutorial 70 | (:use #:cl) 71 | (:export #:hello)) 72 | (in-package #:tutorial) 73 | (named-readtables:in-readtable papyrus:md-syntax) 74 | 75 | # My First Document 76 | 77 | This is my first document. 78 | This will say "Hello, world!". 79 | 80 | ```lisp 81 | (defun hello () 82 | (princ "Hello, world!")) 83 | ``` 84 | 85 | If you try this tutorial, save it as `tutorial.md`, as this is the filename 86 | used in this section. Now, there are two ways to generate the document, 87 | **REPL** and **ASDF**. The following are quick tutorials for each. For more 88 | information, please see the **Reference** section. 89 | 90 | ### REPL 91 | 92 | A REPL is a good environment to experiment with your *Papyrus* documents. We 93 | can load them and test the behaivor quickly and it is convenient to use them 94 | with *SLIME*. 95 | 96 | #### Installation 97 | 98 | *Papyrus* is available in QuickLisp. 99 | To install Just type, 100 | 101 | > (ql:quickload :papyrus) 102 | 103 | Or, you can install *Papyrus* with [Roswell](https://github.com/roswell/roswell). 104 | 105 | $ ros install tani/papyrus 106 | 107 | Next you can load document as follows: 108 | 109 | > (require :papyrus) 110 | nil 111 | > (load #p"tutorial.md") 112 | nil 113 | > (tutorial:hello) 114 | Hello, World! 115 | 116 | ### ASDF 117 | 118 | Let's write a small project whose files are the following. 119 | 120 | tutorial.asd 121 | tutorial.md 122 | 123 | `tutorial.md` is the file written in the **REPL** section, and 124 | `tutorial.asd` is this: 125 | 126 | (defclass md (cl-source-file) 127 | ((type :initform "md"))) 128 | 129 | (defclass org (cl-source-file) 130 | ((type :initform "org"))) 131 | 132 | (defclass pod (cl-source-file) 133 | ((type :initform "pod"))) 134 | 135 | (defsystem tutorial 136 | :version "0.1" 137 | :author "Your name" 138 | :license "MIT" 139 | :depends-on (#:papyrus #:named-readtables) 140 | :components ((:md "tutorial")) 141 | :description "A Literate Programming Framework") 142 | 143 | Now that you have both files, `tutorial.md` and `tutorial.asd`, 144 | you will be able to load this system like this. 145 | 146 | > (load #p"tutorial.asd") 147 | nil 148 | > (require :tutorial) 149 | nil 150 | > (tutorial:hello) 151 | Hello, World! 152 | 153 | Of course, users of your project won't need to load anything else. 154 | 155 | ## Reference 156 | 157 | ### `enable-md-syntax` 158 | 159 | This is a readtable defined for Markdown. 160 | The codeblock enclosed by ` ```lisp ` and ` ``` ` is evaluated. 161 | 162 | (defpackage #:sample 163 | (:use #:cl) 164 | (:export #:sample-function)) 165 | (in-package #:sample) 166 | (named-readtables:in-readtable papyrus:md-syntax) 167 | 168 | # Sample 169 | 170 | This is a sample code. The following function just says "Hello, world!" 171 | 172 | ```lisp 173 | (defun sample-function () (princ "Hello, world!")) 174 | ``` 175 | 176 | ### `enable-org-syntax` 177 | 178 | This is a readtable for org-mode. 179 | The codeblock enclosed by ` #+BEGIN_SRC lisp :tangle yes ` and ` #+END_SRC ` is evaluated. 180 | Unlike Markdown, any `#+CL:` tags are ignored when rendering the content to HTML. 181 | 182 | #+CL:* * (defpackage #:sample 183 | #+CL:* * (:use #:cl) 184 | #+CL:* * (:export #:sample-function)) 185 | #+CL:* * (in-package #:sample) 186 | #+CL:* * (named-readtables:in-readtable papyrus:org-syntax) 187 | 188 | This is a sample code. The following function just says "Hello, world!" 189 | 190 | #+BEGIN_SRC lisp :tangle yes 191 | (defun sample-function () (princ "Hello, world!")) 192 | #+END_SRC 193 | 194 | ### `enable-pod-syntax` 195 | 196 | This is a readtable defined by `named-readtables` for POD. 197 | The codeblock outside of `=pod` and `=cut` is evaluated. 198 | This syntax is similar to Perl's POD. 199 | 200 | (defpackage #:sample 201 | (:use #:cl) 202 | (:export #:sample-function)) 203 | (in-package #:sample) 204 | (named-readtables:in-readtable papyrus:pod-syntax) 205 | 206 | =pod 207 | 208 | This is a sample code. The following function just says "Hello, world!" 209 | 210 | =cut 211 | 212 | (defun sample-function () (princ "Hello, world!")) 213 | 214 | ## Appendix 215 | 216 | ### Nix overlays 217 | - [nix flakes without any frameworks.](https://github.com/tani/nix-common-lisp?tab=readme-ov-file#overlays) 218 | ```nix 219 | inputs = { 220 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 221 | flake-parts.url = "github:hercules-ci/flake-parts"; 222 | papyrus = { 223 | url = "github:tani/papyrus"; 224 | inputs.nixpkgs.follows = "nixpkgs"; 225 | }; 226 | }; 227 | ``` 228 | ```nix 229 | let 230 | pkgs = import nixpkgs { 231 | inherit system; 232 | overlays = [ 233 | inputs.papyrus.overlays.default 234 | ]; 235 | }; 236 | ``` 237 | - [flake-parts](https://flake.parts/overlays.html?highlight=overlays#consuming-an-overlay) 238 | ```nix 239 | inputs = { 240 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 241 | flake-parts.url = "github:hercules-ci/flake-parts"; 242 | papyrus = { 243 | url = "github:tani/papyrus"; 244 | inputs.nixpkgs.follows = "nixpkgs"; 245 | }; 246 | }; 247 | ``` 248 | ```nix 249 | perSystem = { system, ... }: { 250 | _module.args.pkgs = import inputs.nixpkgs { 251 | inherit system; 252 | overlays = [ 253 | inputs.papyrus.overlays.default 254 | ]; 255 | config = { }; 256 | }; 257 | }; 258 | ``` 259 | ### Emacs Lisp 260 | 261 | ### Recommended way 262 | 263 | Try to use [polymode](https://github.com/vspinu/polymode) 264 | 265 | (require 'poly-markdown) 266 | (add-to-list 'auto-mode-alist '("\\.md" . poly-markdown-mode)) 267 | 268 | ### Old way 269 | 270 | If you use emacs, there is `mmm-mode` which highlights the syntax of lisp 271 | codeblocks in Markdown, but SLIME doesn't works well in `mmm-mode`. 272 | 273 | (require 'mmm-mode) 274 | (setq mmm-global-mode 'maybe) 275 | (set-face-background 'mmm-default-submode-face nil) 276 | (mmm-add-mode-ext-class nil "\\.md?\\'" 'lisp-markdown) 277 | (mmm-add-classes 278 | '((lisp-markdown 279 | :submode lisp-mode 280 | :front "```lisp" 281 | :back "```"))) 282 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-parts": { 4 | "inputs": { 5 | "nixpkgs-lib": "nixpkgs-lib" 6 | }, 7 | "locked": { 8 | "lastModified": 1733312601, 9 | "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", 10 | "owner": "hercules-ci", 11 | "repo": "flake-parts", 12 | "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "hercules-ci", 17 | "repo": "flake-parts", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1735563628, 24 | "narHash": "sha256-OnSAY7XDSx7CtDoqNh8jwVwh4xNL/2HaJxGjryLWzX8=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "b134951a4c9f3c995fd7be05f3243f8ecd65d798", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "nixos-24.05", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs-lib": { 38 | "locked": { 39 | "lastModified": 1733096140, 40 | "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", 41 | "type": "tarball", 42 | "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 43 | }, 44 | "original": { 45 | "type": "tarball", 46 | "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" 47 | } 48 | }, 49 | "root": { 50 | "inputs": { 51 | "flake-parts": "flake-parts", 52 | "nixpkgs": "nixpkgs" 53 | } 54 | } 55 | }, 56 | "root": "root", 57 | "version": 7 58 | } 59 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A flake for papyrus"; 3 | inputs = { 4 | nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; 5 | flake-parts.url = "github:hercules-ci/flake-parts"; 6 | }; 7 | outputs = inputs@{ nixpkgs, flake-parts, ... }: 8 | flake-parts.lib.mkFlake { inherit inputs; } { 9 | imports = [ flake-parts.flakeModules.easyOverlay ]; 10 | systems = nixpkgs.lib.platforms.all; 11 | perSystem = { config, pkgs, lib, system, ... }: 12 | let 13 | ############ Settings ############ 14 | ## Project name 15 | pname = "papyrus"; 16 | ## Source directory 17 | src = ./.; 18 | ## Dependencies 19 | lispLibs = lisp: with lisp.pkgs; [ named-readtables fiveam ]; 20 | ## Non-Lisp dependencies 21 | nativeLibs = with pkgs; [ ]; 22 | ## Supported Lisp implementations 23 | lispImpls = [ 24 | "abcl" 25 | "sbcl" 26 | "ecl" 27 | "ccl" 28 | # "mkcl" 29 | "clisp" 30 | "cmucl_binary" 31 | "clasp-common-lisp" 32 | ]; 33 | ################################## 34 | systems = let 35 | asd = builtins.readFile "${src}/${pname}.asd"; 36 | res = builtins.split ''defsystem[[:space:]]*([^[:space:]]*)'' asd; 37 | odd = n: lib.trivial.mod n 2 == 1; 38 | sys1 = lib.lists.flatten (lib.lists.ifilter0 (i: v: odd i) res); 39 | sys2 = builtins.map (s: builtins.replaceStrings [''"'' "#:" ":"] ["" "" ""] s) sys1; 40 | in sys2; 41 | versions = let 42 | asd = builtins.readFile "${src}/${pname}.asd"; 43 | res = builtins.split '':version[[:space:]]*([^[:space:]]*)'' asd; 44 | odd = n: lib.trivial.mod n 2 == 1; 45 | ver1 = lib.lists.flatten (lib.lists.ifilter0 (i: v: odd i) res); 46 | ver2 = builtins.map (s: builtins.replaceStrings [''"''] [""] s) ver1; 47 | in ver2; 48 | version = builtins.head versions; 49 | isAvailable = impl: let 50 | basePkgs = import nixpkgs { inherit system; overlays = []; }; 51 | lisp = basePkgs.${impl}; 52 | in (builtins.tryEval lisp).success 53 | && (builtins.elem system lisp.meta.platforms) 54 | && (!lisp.meta.broken); 55 | availableLispImpls = builtins.filter isAvailable lispImpls; 56 | LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath nativeLibs; 57 | unbundledPackage = { lisp, mainProgram, evalFlag, extraArgs }: rec { 58 | mainLib = lisp.buildASDFSystem { 59 | inherit pname version src systems nativeLibs; 60 | lispLibs = lispLibs lisp; 61 | }; 62 | lisp' = lisp.withPackages (ps: [ mainLib ]) // { 63 | inherit (lisp) meta; 64 | }; 65 | mainExe = pkgs.writeShellScriptBin pname '' 66 | export LD_LIBRARY_PATH=${LD_LIBRARY_PATH} 67 | ${lisp'}/bin/${mainProgram} ${extraArgs} ${evalFlag} '(require "asdf")' ${evalFlag} "$(cat <