changelog shortlog graph tags branches files raw help

Mercurial > core / changeset: migrating

changeset 3: 4dbfe05407bd
parent 2: ca8af026ee3d
child 4: 7e5b3feb2c38
author: ellis <ellis@rwest.io>
date: Sun, 15 Oct 2023 03:20:41 -0400
files: .hgsubstate lisp/lib/cli.asd lisp/lib/cli/cli.lisp lisp/lib/rdb.asd lisp/lib/rdb/rdb.lisp lisp/lib/rdb/tests.lisp lisp/lib/rt.asd lisp/lib/rt/rt.lisp lisp/lib/sxp.asd lisp/lib/sxp/bench.lisp lisp/lib/sxp/sxp.lisp lisp/lib/sxp/tests.lisp lisp/lib/sxp/tests.sxp lisp/rdb.asd lisp/rdb/rdb.lisp lisp/rdb/tests.lisp lisp/sxp.asd lisp/sxp/bench.lisp lisp/sxp/sxp.lisp lisp/sxp/tests.lisp lisp/sxp/tests.sxp readme.org
description: migrating
     1.1--- a/.hgsubstate	Sun Oct 15 01:50:27 2023 -0400
     1.2+++ b/.hgsubstate	Sun Oct 15 03:20:41 2023 -0400
     1.3@@ -1,3 +1,3 @@
     1.4-0000000000000000000000000000000000000000 lisp/macs
     1.5-080137fb0579395424bb96622252d1ddb3dade5d lisp/organ
     1.6-6b7881cc28419cd135ce64216267a42a0960eb10 lisp/skel
     1.7+4685181e4be84baada5b21327b51bc5616fc4cae lisp/macs
     1.8+0000000000000000000000000000000000000000 lisp/organ
     1.9+0000000000000000000000000000000000000000 lisp/skel
     2.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2+++ b/lisp/lib/cli.asd	Sun Oct 15 03:20:41 2023 -0400
     2.3@@ -0,0 +1,9 @@
     2.4+(defsystem "cli"
     2.5+  :version "0.1.0"
     2.6+  :author "ellis <ellis@rwest.io>"
     2.7+  :description "command-line ui framework"
     2.8+  :bug-tracker "https://lab.rwest.io/ellis/macs/issues"
     2.9+  :source-control (:hg "https://lab.rwest.io/ellis/macs")
    2.10+  :depends-on (:macs :sxp)
    2.11+  :in-order-to ((test-op (test-op "macs/tests")))
    2.12+  :components ((:file "cli")))
     3.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2+++ b/lisp/lib/cli/cli.lisp	Sun Oct 15 03:20:41 2023 -0400
     3.3@@ -0,0 +1,781 @@
     3.4+;;; cli.lisp --- cli programming api and utils
     3.5+
     3.6+;; This package contains a simple api and macros for building lisp CLI
     3.7+;; programs.
     3.8+
     3.9+;;; Commentary:
    3.10+
    3.11+;; - inspired by: clingon, uiop
    3.12+
    3.13+;; Basic assumptions at runtime:
    3.14+;;   - running in a POSIX-compliant shell
    3.15+;;   - output stream supports UTF-8
    3.16+
    3.17+;; TODO 2023-10-14: install-ast, install-thunk, proc-args, etc should
    3.18+;; return IR types - CLI-IR THUNK and CLI-IR respectively.
    3.19+
    3.20+;; TODO 2023-10-14: rename cli-ast to cli-ir, install-ast to
    3.21+;; install-ir, etc.
    3.22+
    3.23+;;; Code:
    3.24+(pkg:defpkg :cli
    3.25+  (:use :cl :sym :cond :fu :str :ana :fmt :log)
    3.26+  (:import-from :ana :alet)
    3.27+  (:import-from :uiop :println)
    3.28+  (:import-from :sb-posix :filename-designator)
    3.29+  (:import-from :sb-ext :parse-native-namestring)
    3.30+  (:shadowing-import-from :sb-ext :exit)
    3.31+  (:export
    3.32+   :*argv*
    3.33+   :init-args
    3.34+   :cli-arg0
    3.35+   :cli-args
    3.36+   :command-line-args
    3.37+   :*cli-group-separator*
    3.38+   :*cli-opt-kinds*
    3.39+   :global-opt-p
    3.40+   :exec-path-list
    3.41+   :argp
    3.42+   :$val
    3.43+   :$args
    3.44+   :$argc
    3.45+   :$opts
    3.46+   :$optc
    3.47+   :make-shorty
    3.48+   :with-cli-handlers
    3.49+   :completing-read
    3.50+   :make-prompt!
    3.51+   :defmain
    3.52+   :main
    3.53+   :with-cli
    3.54+   :make-cli
    3.55+   ;; opt-parsers
    3.56+   :make-opt-parser
    3.57+   :parse-bool-opt
    3.58+   :parse-str-opt
    3.59+   :parse-form-opt
    3.60+   :parse-list-opt
    3.61+   :parse-sym-opt
    3.62+   :parse-key-opt
    3.63+   :parse-num-opt
    3.64+   :parse-str-opt
    3.65+   :parse-file-opt
    3.66+   :parse-dir-opt
    3.67+   :make-opts
    3.68+   :make-cmds
    3.69+   :active-opts
    3.70+   :active-cmds
    3.71+   :proc-args
    3.72+   :make-cli-node
    3.73+   :make-cli-ast
    3.74+   :proc-args
    3.75+   :parse-args
    3.76+   :debug-opts
    3.77+   :do-cmd
    3.78+   :do-opt
    3.79+   :call-opt
    3.80+   :call-cmd
    3.81+   :apply-cmd
    3.82+   :print-help
    3.83+   :print-version
    3.84+   :print-usage
    3.85+   :handle-unknown-argument
    3.86+   :handle-missing-argument
    3.87+   :handle-invalid-argument
    3.88+   :cli-opt
    3.89+   :cli-val
    3.90+   :cli-cmd-args
    3.91+   :cli-cmd
    3.92+   :cli-cwd
    3.93+   :find-cmd
    3.94+   :find-opt
    3.95+   :find-short-opt
    3.96+   :install-ast
    3.97+   ;; :gen-cli-thunk
    3.98+   :install-thunk
    3.99+   :cli
   3.100+   :cli-equal
   3.101+   :defopt
   3.102+   :defcmd
   3.103+   :define-cli
   3.104+   ;; ast types
   3.105+   :opt
   3.106+   :cmd
   3.107+   :arg
   3.108+   :cli-name
   3.109+   :cli-opts
   3.110+   :cli-cmds
   3.111+   :cli-thunk
   3.112+   :cli-description
   3.113+   :cli-version
   3.114+   :cli-usage))
   3.115+
   3.116+(in-package :cli)
   3.117+
   3.118+(defun cli-arg0 () (car sb-ext:*posix-argv*))
   3.119+(defun cli-args () (cdr sb-ext:*posix-argv*))
   3.120+
   3.121+(declaim (inline exec-path-list))
   3.122+(defun exec-path-list ()
   3.123+  (let ((var (sb-posix:getenv "PATH")))
   3.124+    (mapcar #'directory
   3.125+	    (loop for i = 0 then (1+ j)
   3.126+		  as j = (position #\: var :start i)
   3.127+		  collect (subseq var i j)
   3.128+		while j))))
   3.129+
   3.130+(defparameter *cli-group-separator*
   3.131+  "--"
   3.132+  "A marker specifying the end of a unique group of CLI args.")
   3.133+
   3.134+;; uiop:command-line-arguments
   3.135+
   3.136+;;; Macros
   3.137+(defmacro argp (arg &optional (args (cli-args)))
   3.138+  "Test for presence of ARG in ARGS. Return the tail of
   3.139+ARGS starting from the position of ARG."
   3.140+  `(member ,arg ,args :test #'string=))
   3.141+
   3.142+(defmacro make-shorty (name)
   3.143+  "Return the first char of symbol or string NAME."
   3.144+  `(character (aref (if (stringp ,name) ,name (symbol-name ,name)) 0)))
   3.145+
   3.146+;; (defun treat-as-argument (condition)
   3.147+;;   "A handler which can be used to invoke the `treat-as-argument' restart"
   3.148+;;   (invoke-restart (find-restart 'treat-as-argument condition)))
   3.149+
   3.150+;; (defun discard-argument (condition)
   3.151+;;   "A handler which can be used to invoke the `discard-argument' restart"
   3.152+;;   (invoke-restart (find-restart 'discard-argument condition)))
   3.153+
   3.154+(defmacro with-cli-handlers (form)
   3.155+  "A wrapper which handles common cli errors that may occur during
   3.156+evaluation of FORM."
   3.157+  `(handler-case ,form
   3.158+     (sb-sys:interactive-interrupt ()
   3.159+       (format *error-output* "~&(:SIGINT)~&")
   3.160+       (exit :code 130))
   3.161+     (error (c)
   3.162+       (format *error-output* "~&~A~&" c)
   3.163+       (exit :code 1))))
   3.164+
   3.165+(defun init-args () (setq *argv* (cons (cli-arg0) (cli-args))))
   3.166+
   3.167+(defmacro with-cli (slots cli &body body)
   3.168+  "Like with-slots with some extra bindings."
   3.169+  ;; (with-gensyms (cli-body)
   3.170+  ;;  (let ((cli-body (mapcar (lambda (x) ()) cli-body)
   3.171+  `(progn
   3.172+     (init-args)
   3.173+     (setf (cli-cwd ,cli) (sb-posix:getcwd))
   3.174+     (with-slots ,slots (parse-args ,cli *argv* :compile t)
   3.175+       ,@body)))
   3.176+
   3.177+;;; Prompts
   3.178+(declaim (inline completing-read))
   3.179+(defun completing-read (prompt collection
   3.180+			&key (history nil) (default nil)
   3.181+			(key nil) (test nil))
   3.182+
   3.183+  "A simplified COMPLETING-READ for common-lisp.
   3.184+
   3.185+The Emacs completion framework includes a function called
   3.186+`completing-read' which prompts the user for input from the
   3.187+mini-buffer. It is a very flexible interface which can be used to read
   3.188+user input programatically. This is incredibly useful for building
   3.189+data entry interfaces -- for example see the `make-prompt!' macro.
   3.190+
   3.191+Obviously writing a completion framework is out-of-scope, but we can
   3.192+simulate one by embedding a DSL in our prompters if we choose. For
   3.193+example, perhaps we treat a single '?' character as a request from the
   3.194+user to list valid options while continue waiting for input."
   3.195+  (princ prompt)
   3.196+  ;; ensure we empty internal buffer
   3.197+  (finish-output)
   3.198+  (let* ((coll (symbol-value collection))
   3.199+	 (r (if coll
   3.200+		(find (read-line) coll :key key :test test)
   3.201+		(or (read-line) default))))
   3.202+    (prog1
   3.203+	r
   3.204+    (setf (symbol-value history) (push r history)))))
   3.205+
   3.206+(defmacro make-prompt! (var &optional prompt)
   3.207+  "Generate a 'prompter' from list or variable VAR and optional
   3.208+PROMPT string.
   3.209+
   3.210+This isn't an ideal solution as it does in fact expose a dynamic
   3.211+variable (VAR-prompt-history). We should generate accessors and
   3.212+keep the variables within lexical scope of the generated
   3.213+closure."
   3.214+  (with-gensyms (s p h)
   3.215+    `(let ((,s (if (boundp ',var) (symbol-value ',var) 
   3.216+		   (progn 
   3.217+		     (defvar ,(symb var) nil)
   3.218+		     ',(symb var))))
   3.219+           (,p (when (stringp ,prompt) ,prompt)) ;; prompt string
   3.220+           (,h ',(symb var '-prompt-history))) ;; history symbol
   3.221+       (defvar ,(symb var '-prompt-history) nil)
   3.222+       (defun ,(symb var '-prompt) ()
   3.223+	 ,(format nil "Prompt for a value from `~A', use DEFAULT if non-nil
   3.224+and no value is provided by user, otherwise fallback to the `car'
   3.225+of `~A-PROMPT-HISTORY'." var var)
   3.226+	 (completing-read
   3.227+          (format nil "~A [~A]: "
   3.228+		  (or ,p ">")
   3.229+		  (car (symbol-value ,h)))
   3.230+	  ,s :history ,h :default nil)))))
   3.231+
   3.232+(defmacro define-cli-constant (name cli &optional doc)
   3.233+  `(define-constant ,name ,cli ,@doc :test #'cli-equal))
   3.234+
   3.235+(defvar *default-cli-def* 'defparameter)
   3.236+
   3.237+(defmacro defcmd (name &body body)
   3.238+  `(defun ,name ($args $opts) 
   3.239+     (declare (ignorable $args $opts))
   3.240+     (let (($argc (length $args))
   3.241+	   ($optc (length $opts)))
   3.242+       (declare (ignorable $argc $optc))
   3.243+       ,@body)))
   3.244+
   3.245+(defmacro defopt (name &body body)
   3.246+  `(defun ,name ($val)
   3.247+     (declare (ignorable $val))
   3.248+     ,@body))
   3.249+
   3.250+(declaim (inline walk-cli-slots))
   3.251+(defun walk-cli-slots (cli)
   3.252+  "Walk the plist CLI, performing actions as necessary based on the slot
   3.253+keys."
   3.254+  (loop for kv in (group cli 2)
   3.255+	when (eql :thunk (car kv))
   3.256+	  return (let ((th (cdr kv)))
   3.257+		   (if (or (functionp th) (symbolp th)) (funcall th) (compile nil (lambda () th)))))
   3.258+	cli)
   3.259+
   3.260+(defmacro define-cli (name &body body)
   3.261+  "Define a symbol NAME bound to a top-level CLI object."
   3.262+  (declare (type symbol name))
   3.263+      `(,*default-cli-def* ,name (apply #'make-cli t (walk-cli-slots ',body))))
   3.264+
   3.265+(defmacro defmain (ret &body body)
   3.266+  "Define a main function in the current package which returns RET.
   3.267+
   3.268+Note that this macro does not export the defined function and requires
   3.269+`cli:main' to be an external symbol."
   3.270+  `(progn
   3.271+     (declaim (type stream output))
   3.272+     (defun main (&key (output *standard-output*))
   3.273+       "Run the top-level function and print to OUTPUT."
   3.274+       (let ((*standard-output* output))
   3.275+	 (with-cli-handlers
   3.276+	     (progn ,@body ,ret))))))
   3.277+
   3.278+;;; Utils
   3.279+(defvar *argv*)
   3.280+
   3.281+(defun make-cli (kind &rest slots)
   3.282+  "Creates a new CLI object of the given kind."
   3.283+  (declare (type (member :opt :cmd :cli t) kind))
   3.284+  (apply #'make-instance
   3.285+   (cond
   3.286+     ((eql kind :cli) 'cli)
   3.287+     ((eql kind :opt) 'cli-opt)
   3.288+     ((eql kind :cmd) 'cli-cmd)
   3.289+     (t 'cli))
   3.290+    slots))
   3.291+
   3.292+;; RESEARCH 2023-09-12: closed over hash-table with short/long flags
   3.293+;; to avoid conflicts. if not, need something like a flag-function
   3.294+;; slot at class allocation.
   3.295+(defmacro make-opts (&body opts)
   3.296+  `(map 'vector
   3.297+	(lambda (x)
   3.298+	  (etypecase x
   3.299+	    (string (make-cli :opt :name x))
   3.300+	    (list (apply #'make-cli :opt x))
   3.301+	    (t (make-cli :opt :name (format nil "~(~A~)" x) :global t))))
   3.302+	(walk-cli-slots ',opts)))
   3.303+
   3.304+(defmacro make-cmds (&body opts)
   3.305+  `(map 'vector
   3.306+	(lambda (x)
   3.307+	  (etypecase x
   3.308+	    (string (make-cli :cmd :name x))
   3.309+	    (list (apply #'make-cli :cmd x))
   3.310+	    (t (make-cli :cmd :name (format nil "~(~A~)" x)))))
   3.311+	(walk-cli-slots ',opts)))
   3.312+
   3.313+(defun long-opt-p (str)
   3.314+  (and (char= (aref str 0) (aref str 1) #\-)
   3.315+       (> (length str) 2)))
   3.316+
   3.317+(defun short-opt-p (str)
   3.318+  (and (char= (aref str 0) #\-)
   3.319+       (not (char= (aref str 1) #\-))
   3.320+       (> (length str) 1)))
   3.321+
   3.322+(defun opt-group-p (str)
   3.323+  (string= str *cli-group-separator*))
   3.324+
   3.325+(defun opt-prefix-eq (ch str)
   3.326+  (char= (aref str 0) ch))
   3.327+
   3.328+(defun gen-thunk-ll (origin args)
   3.329+  (let ((a0 (list (symb '$a 0) origin)))
   3.330+    (group 
   3.331+     (nconc (loop for i from 1 for a in args nconc (list (symb '$a i) a)) a0 )
   3.332+     2)))
   3.333+
   3.334+;; TODO 2023-10-06: 
   3.335+;; (defmacro gen-cli-thunk (pvars &rest thunk)
   3.336+;;   "Generate and return a function based on THUNK suitable for the :thunk
   3.337+;; slot of cli objects with pandoric bindings PVARS.")
   3.338+
   3.339+;;; Protocol
   3.340+(defgeneric push-cmd (cmd place))
   3.341+
   3.342+(defgeneric push-opt (opt place))
   3.343+
   3.344+(defgeneric pop-cmd (place))
   3.345+
   3.346+(defgeneric pop-opt (place))
   3.347+
   3.348+(defgeneric find-cmd (self name &optional active))
   3.349+
   3.350+(defgeneric find-opt (self name &optional active))
   3.351+
   3.352+(defgeneric active-cmds (self))
   3.353+
   3.354+(defgeneric active-opts (self &optional global))
   3.355+
   3.356+(defgeneric find-short-opt (self ch))
   3.357+
   3.358+(defgeneric call-opt (self arg))
   3.359+
   3.360+(defgeneric do-opt (self))
   3.361+
   3.362+(defgeneric call-cmd (self args opts))
   3.363+
   3.364+(defgeneric parse-args (self args &key &allow-other-keys)
   3.365+  (:documentation "Parse list of strings ARGS using SELF.
   3.366+
   3.367+A list of the same length as ARGS is returned containing 'cli-ast'
   3.368+objects: (OPT . (or char string)) (CMD . string) NIL"))
   3.369+
   3.370+(defgeneric do-cmd (self)
   3.371+  (:documentation "Run the command SELF with args parsed at runtime."))
   3.372+
   3.373+(defgeneric print-help (self &optional stream)
   3.374+  (:documentation "Format cli SELF as a helpful string."))
   3.375+
   3.376+(defgeneric print-version (self &optional stream)
   3.377+  (:documentation "Print the version of SELF."))
   3.378+
   3.379+(defgeneric print-usage (self &optional stream)
   3.380+  (:documentation "Format cli SELF as a useful string."))
   3.381+
   3.382+(defgeneric handle-unknown-argument (self arg)
   3.383+  (:documentation "Handle an unknown argument."))
   3.384+
   3.385+(defgeneric handle-missing-argument (self arg)
   3.386+  (:documentation "Handle a missing argument."))
   3.387+
   3.388+(defgeneric handle-invalid-argument (self arg)
   3.389+  (:documentation "Handle an invalid argument."))
   3.390+
   3.391+(defgeneric cli-equal (a b))
   3.392+
   3.393+(defun default-thunk (cli) (lambda (x) (declare (ignore x)) (print-help cli)))
   3.394+
   3.395+(defvar *cli-opt-kinds* '(bool str form list sym key num file dir))
   3.396+
   3.397+(defun cli-opt-kind-p (s)
   3.398+  (declare (type symbol s))
   3.399+  (find s *cli-opt-kinds*))
   3.400+
   3.401+(defmacro make-opt-parser (kind-spec &body body)
   3.402+  "Return a KIND-opt-parser function based on KIND-SPEC which is either a
   3.403+symbol from *cli-opt-kinds* or a list, and optional BODY which
   3.404+is a list of handlers for the opt-val."
   3.405+  (let* ((kind (if (consp kind-spec) (car kind-spec) kind-spec))
   3.406+	 (super (when (consp kind-spec) (cadr kind-spec)))
   3.407+	 (fn-name (symb 'parse- kind '-opt)))
   3.408+    ;; thread em
   3.409+    (let ((fn1 (when (not (eql 'nil super)) (symb 'parse- super '-opt))))
   3.410+      `(progn
   3.411+	 (defun ,fn-name ($val)
   3.412+	   "Parse the cli-opt-val $VAL."
   3.413+	   ;; do stuff
   3.414+	   (when (not (eql ',fn1 'nil)) (setq $val (funcall ',fn1 $val)))
   3.415+	   ,@body)))))
   3.416+
   3.417+(make-opt-parser bool $val)
   3.418+
   3.419+(make-opt-parser (str bool) (when (stringp $val) $val))
   3.420+
   3.421+(make-opt-parser (form str) (read-from-string $val))
   3.422+
   3.423+(make-opt-parser (list form) (when (listp $val) $val))
   3.424+
   3.425+(make-opt-parser (sym form) (when (symbolp $val) $val))
   3.426+
   3.427+(make-opt-parser (key form) (when (keywordp $val) $val))
   3.428+
   3.429+(make-opt-parser (num form) (when (numberp $val) $val))
   3.430+
   3.431+(make-opt-parser (file str) 
   3.432+  (when $val (parse-native-namestring $val nil *default-pathname-defaults* :as-directory nil)))
   3.433+
   3.434+(make-opt-parser (dir str) 
   3.435+  (when $val (sb-ext:parse-native-namestring $val nil *default-pathname-defaults* :as-directory t)))
   3.436+
   3.437+;;; Objects
   3.438+(defclass cli-opt ()
   3.439+  ;; note that cli-opts can have a nil or unbound name slot
   3.440+  ((name :initarg :name :initform (required-argument :name) :accessor cli-name :type string)
   3.441+   (kind :initarg :kind :initform 'boolean :accessor cli-opt-kind :type cli-opt-kind-p)
   3.442+   (thunk :initform #'default-thunk :initarg :thunk :type function-lambda-expression :accessor cli-thunk)
   3.443+   (val :initarg :val :initform nil :accessor cli-val :type form)
   3.444+   (global :initarg :global :initform nil :accessor global-opt-p :type boolean)
   3.445+   (description :initarg :description :accessor cli-description :type string)
   3.446+   (lock :initform nil :initarg :lock :accessor cli-lock-p :type boolean))
   3.447+  (:documentation "CLI option"))
   3.448+
   3.449+(defmethod handle-unknown-argument ((self cli-opt) arg))
   3.450+(defmethod handle-missing-argument ((self cli-opt) arg))
   3.451+(defmethod handle-invalid-argument ((self cli-opt) arg))
   3.452+
   3.453+(defmethod initialize-instance :after ((self cli-opt) &key)
   3.454+  (with-slots (name thunk) self
   3.455+    (unless (stringp name) (setf name (format nil "~(~A~)" name)))
   3.456+    (when (symbolp thunk) (setf thunk (funcall (compile nil `(lambda () ,(symbol-function thunk))))))
   3.457+    self))
   3.458+
   3.459+(defmethod install-thunk ((self cli-opt) (lambda function) &optional compile)
   3.460+  "Install THUNK into the corresponding slot in cli-cmd SELF."
   3.461+  (let ((%thunk (if compile (compile nil lambda) lambda)))
   3.462+    (setf (cli-thunk self) %thunk)
   3.463+    self))
   3.464+
   3.465+(defmethod print-object ((self cli-opt) stream)
   3.466+  (print-unreadable-object (self stream :type t)
   3.467+    (format stream "~A :global ~A :val ~A"
   3.468+            (cli-name self)
   3.469+	    (global-opt-p self)
   3.470+	    (cli-val self))))
   3.471+
   3.472+(defmethod print-usage ((self cli-opt) &optional stream)
   3.473+  (format stream " -~(~{~A~^/--~}~)~A~A"
   3.474+	  (if-let ((n (cli-name self)))
   3.475+	    (list (make-shorty n) n)
   3.476+	    'dyn)
   3.477+	  (if (global-opt-p self) "* " "  ")
   3.478+	  (if-let ((d (and (slot-boundp self 'description) (cli-description self))))
   3.479+	    (format stream ":  ~A" d)
   3.480+	    "")))
   3.481+
   3.482+(defmethod cli-equal ((a cli-opt) (b cli-opt))
   3.483+  (with-slots (name global kind) a
   3.484+    (with-slots ((bn name) (bg global) (bk kind)) b
   3.485+      (and (string= name bn)
   3.486+	   (eql global bg)
   3.487+	   (eql kind bk)))))
   3.488+
   3.489+(defmethod call-opt ((self cli-opt) arg)
   3.490+  (funcall (compile nil (cli-thunk self)) arg))
   3.491+
   3.492+(defmethod do-opt ((self cli-opt))
   3.493+  (call-opt self (cli-val self)))
   3.494+
   3.495+(defclass cli-cmd ()
   3.496+  ;; name slot is required and must be a string
   3.497+  ((name :initarg :name :initform (required-argument :name) :accessor cli-name :type string)
   3.498+   (opts :initarg :opts :initform (make-array 0 :element-type 'cli-opt)
   3.499+	 :accessor cli-opts :type (vector cli-opt))
   3.500+   (cmds :initarg :cmds :initform (make-array 0 :element-type 'cli-cmd)
   3.501+	 :accessor cli-cmds :type (vector cli-cmd))
   3.502+   (thunk :initform #'default-thunk :initarg :thunk :accessor cli-thunk :type function-lambda-expression)
   3.503+   (lock :initform nil :initarg :lock :accessor cli-lock-p :type boolean)
   3.504+   (description :initarg :description :accessor cli-description :type string)
   3.505+   (args :initform nil :initarg :args :accessor cli-cmd-args))
   3.506+  (:documentation "CLI command"))
   3.507+
   3.508+(defmethod initialize-instance :after ((self cli-cmd) &key)
   3.509+  (with-slots (name cmds opts thunk) self
   3.510+    (unless (stringp name) (setf name (format nil "~(~A~)" name)))
   3.511+    (unless (vectorp cmds) (setf cmds (funcall (compile nil `(lambda () ,cmds)))))
   3.512+    (unless (vectorp opts) (setf opts (funcall (compile nil `(lambda () ,opts)))))
   3.513+    (when (symbolp thunk) (setf thunk (symbol-function thunk)))
   3.514+    self))
   3.515+
   3.516+(defmethod print-object ((self cli-cmd) stream)
   3.517+  (print-unreadable-object (self stream :type t)
   3.518+    (format stream "~A :opts ~A :cmds ~A :args ~A"
   3.519+	  (cli-name self)
   3.520+          (length (cli-opts self))
   3.521+	  (length (cli-cmds self))
   3.522+	  (length (cli-cmd-args self)))))
   3.523+
   3.524+(defmethod print-usage ((self cli-cmd) &optional stream)
   3.525+  (with-slots (opts cmds) self
   3.526+    (format stream "~(~A~)  ~A~A~A"
   3.527+	    (cli-name self)
   3.528+	    (if-let ((d (and (slot-boundp self 'description) (cli-description self))))
   3.529+	      (format nil ":  ~A" d)
   3.530+	      "")
   3.531+	    (if (null opts)
   3.532+		""
   3.533+		(format nil "~{~%    ~A~^~}" (loop for o across opts collect (print-usage o nil))))
   3.534+	    (if (null cmds)
   3.535+		""
   3.536+		(format nil "~%    ~{!  ~A~}" (loop for c across cmds collect (print-usage c nil)))))))
   3.537+
   3.538+(defmethod push-cmd ((self cli-cmd) (place cli-cmd))
   3.539+  (vector-push self (cli-cmds place)))
   3.540+
   3.541+(defmethod push-opt ((self cli-opt) (place cli-cmd))
   3.542+  (vector-push self (cli-opts place)))
   3.543+
   3.544+(defmethod pop-cmd ((self cli-cmd))
   3.545+  (vector-pop (cli-cmds self)))
   3.546+
   3.547+(defmethod pop-opt ((self cli-opt))
   3.548+  (vector-pop (cli-opts self)))
   3.549+
   3.550+(defmethod cli-equal ((a cli-cmd) (b cli-cmd))
   3.551+  (with-slots (name opts cmds) a
   3.552+    (with-slots ((bn name) (bo opts) (bc cmds)) b
   3.553+      (and (string= name bn)
   3.554+	   (if (and (null opts) (null bo))
   3.555+	       t
   3.556+	       (unless (member nil (loop for oa across opts
   3.557+					 for ob across bo
   3.558+					 collect (cli-equal oa ob)))
   3.559+		 t))
   3.560+	   (if (and (null cmds) (null bc))
   3.561+	       t
   3.562+	       (unless (member nil (loop for ca across cmds
   3.563+					 for cb across bc
   3.564+					 collect (cli-equal ca cb)))
   3.565+		 t))))))
   3.566+
   3.567+;; typically when starting from a top-level `cli', the global
   3.568+;; `cli-opts' will be parsed first, followed by the first command
   3.569+;; found. If a command is found, the tail of the list is passed as
   3.570+;; arguments to this function, which can pass additonal arguments to
   3.571+;; nested commands.
   3.572+
   3.573+;;  TODO 2023-09-12: Parsing restarts at the `*cli-group-separator*'
   3.574+;; if present, or stops at EOI.
   3.575+
   3.576+(declaim (inline %make-cli-node))
   3.577+(defstruct (cli-node (:constructor %make-cli-node)) kind form)
   3.578+
   3.579+(defun make-cli-node (kind form)
   3.580+  (%make-cli-node :kind kind :form form))
   3.581+
   3.582+(declaim (inline %make-cli-ast))
   3.583+(defstruct (cli-ast (:constructor %make-cli-ast)) ast)
   3.584+
   3.585+(defun make-cli-ast (nodes)  
   3.586+  (%make-cli-ast :ast nodes))
   3.587+
   3.588+(defmethod find-cmd ((self cli-cmd) name &optional active)
   3.589+  (when-let ((c (find name (cli-cmds self) :key #'cli-name :test #'string=)))
   3.590+    (if active 
   3.591+	;; maybe issue warning here? report to user
   3.592+	(when (cli-lock-p c) c)
   3.593+	c)))
   3.594+
   3.595+(defmethod active-cmds ((self cli-cmd))
   3.596+  (remove-if-not #'cli-lock-p (cli-cmds self)))
   3.597+
   3.598+
   3.599+(defmethod find-opt ((self cli-cmd) name &optional active)
   3.600+  (when-let ((o (find name (cli-opts self) :key #'cli-name :test #'string=)))
   3.601+    (if active 
   3.602+	(when (cli-lock-p o) o)
   3.603+	o)))
   3.604+
   3.605+(defun active-global-opt-p (opt)
   3.606+  "Return non-nil if OPT is active at runtime and global."
   3.607+  (when (and (cli-lock-p opt) (global-opt-p opt)) t))
   3.608+
   3.609+(defmethod active-opts ((self cli-cmd) &optional global)
   3.610+  (remove-if-not 
   3.611+   (if global 
   3.612+       #'active-global-opt-p 
   3.613+       #'cli-lock-p)
   3.614+   (cli-opts self)))
   3.615+
   3.616+(defmethod find-short-opt ((self cli-cmd) ch)
   3.617+  (find ch (cli-opts self) :key #'cli-name :test #'opt-prefix-eq))
   3.618+
   3.619+(defmethod proc-args ((self cli-cmd) args)
   3.620+  "process ARGS into an ast. Each element of the ast is a node with a
   3.621+:kind slot, indicating the type of node and a :form slot which stores
   3.622+a value.
   3.623+
   3.624+For now we parse group separators '--' and insert a nil into the tree,
   3.625+this will likely change to generating a new branch in the ast as it
   3.626+should be."
   3.627+  (make-cli-ast
   3.628+   (loop 
   3.629+     for a in args
   3.630+     if (= (length a) 1) collect (make-cli-node 'arg a)
   3.631+     ;; SHORT OPT
   3.632+     else if (short-opt-p a)
   3.633+	    collect (if-let ((o (find-short-opt self (aref a 1))))
   3.634+		      (progn
   3.635+			(setf (cli-val o) t)
   3.636+			(make-cli-node 'opt o))
   3.637+		      (make-cli-node 'arg a))
   3.638+
   3.639+     ;; LONG OPT
   3.640+     else if (long-opt-p a)
   3.641+	    ;; what we actually want to do is consume the next sequence of args - TBD
   3.642+	    collect (if-let ((o (find-opt self (string-trim "-" a))))
   3.643+		      (progn
   3.644+			(setf (cli-val o) (string-trim "-" a))
   3.645+			(make-cli-node 'opt o))
   3.646+		      (make-cli-node 'arg a))
   3.647+     ;; OPT GROUP
   3.648+     else if (opt-group-p a)
   3.649+	    collect nil
   3.650+     ;; CMD
   3.651+     else if (find-cmd self a)
   3.652+	    ;; TBD
   3.653+	    collect (make-cli-node 'cmd (find-cmd self a))
   3.654+     ;; ARG
   3.655+     else collect (make-cli-node 'arg a))))
   3.656+
   3.657+(defmethod install-ast ((self cli-cmd) (ast cli-ast))
   3.658+  "Install the given AST, recursively filling in value slots."
   3.659+  (with-slots (cmds opts) self
   3.660+    ;; we assume all nodes in the ast have been validated and the ast
   3.661+    ;; itself is consumed. validation is performed in proc-args.
   3.662+
   3.663+    ;; before doing anything else we lock SELF, which should remain
   3.664+    ;; locked for the full runtime duration.
   3.665+    (setf (cli-lock-p self) t)
   3.666+    (loop named install
   3.667+	  for (node . tail) on (cli-ast-ast ast)
   3.668+	  unless (null node) 
   3.669+	    do 
   3.670+	       (with-slots (kind form) node
   3.671+		 (case kind
   3.672+		   ;; opts 
   3.673+		   (opt 
   3.674+		    (let ((name (cli-name form))
   3.675+			  (val (cli-val form)))
   3.676+		      (when-let ((o (find-opt self name)))
   3.677+			(setf (cli-val o) val
   3.678+			      (cli-lock-p o) t))))
   3.679+		   ;; when we encounter a command we recurse over the tail
   3.680+		   (cmd 
   3.681+		    (when-let ((c (find-cmd self (cli-name form))))
   3.682+		      (setf (cli-lock-p c) t)
   3.683+		      ;; handle the rest of the AST
   3.684+		      (install-ast c (make-cli-ast tail))
   3.685+		      (return-from install)))
   3.686+		   (arg (push-arg form self)))))
   3.687+    (setf (cli-cmd-args self) (nreverse (cli-cmd-args self)))
   3.688+    self))
   3.689+
   3.690+(defmethod install-thunk ((self cli-cmd) (lambda function) &optional compile)
   3.691+  "Install THUNK into the corresponding slot in cli-cmd SELF."
   3.692+  (let ((%thunk (if compile (compile nil lambda) lambda)))
   3.693+    (setf (cli-thunk self) %thunk)
   3.694+    self))
   3.695+
   3.696+(defmethod push-arg (arg (self cli-cmd))
   3.697+  (push arg (cli-cmd-args self)))
   3.698+
   3.699+(defmethod parse-args ((self cli-cmd) args &key (compile nil))
   3.700+  "Parse ARGS and return the updated object SELF.
   3.701+
   3.702+ARGS is assumed to be a valid cli-ast (list of cli-nodes), unless
   3.703+COMPILE is t, in which case a list of strings is assumed."
   3.704+  (with-slots (opts cmds) self
   3.705+    (let ((args (if compile (proc-args self args) args)))
   3.706+      (print (install-ast self args)))))
   3.707+
   3.708+;; warning: make sure to fill in the opt and cmd slots with values
   3.709+;; from the top-level args before doing a command.
   3.710+(defmethod call-cmd ((self cli-cmd) args opts)
   3.711+  ;; TODO 2023-09-12: handle args/env
   3.712+  (funcall (cli-thunk self) args opts))
   3.713+
   3.714+(defmethod do-cmd ((self cli-cmd))
   3.715+  (call-cmd self (cli-cmd-args self) (cli-opts self)))
   3.716+
   3.717+(defclass cli (cli-cmd)
   3.718+  ;; name slot defaults to *package*, must be string
   3.719+  ((name :initarg :name :initform (string-downcase (package-name *package*)) :accessor cli-name :type string)
   3.720+   (version :initarg :version :initform "0.1.0" :accessor cli-version :type string)
   3.721+   ;; TODO 2023-10-11: look into pushd popd - wd-stack?
   3.722+   (cwd :initarg :cwd :initform (sb-posix:getcwd) :type string :accessor cli-cwd
   3.723+	:documentation "working directory of the top-level CLI."))
   3.724+  (:documentation "CLI"))
   3.725+
   3.726+(defmethod print-usage ((self cli) &optional stream)
   3.727+  (iprintln (format nil "usage: ~A [global] <command> [<arg>]~%" (cli-name self)) 2 stream))
   3.728+
   3.729+(defmethod print-version ((self cli) &optional stream)
   3.730+  (println (cli-version self) stream))
   3.731+
   3.732+(defmethod print-help ((self cli) &optional stream) 
   3.733+  (println (format nil "~A v~A" (cli-name self) (cli-version self)) stream)
   3.734+  (print-usage self stream)
   3.735+  (iprintln (cli-description self) 2 stream)
   3.736+  ;; (terpri stream)
   3.737+  (iprintln "options:" 2 stream)
   3.738+  (with-slots (opts cmds) self
   3.739+    (unless (null opts)
   3.740+      (loop for o across opts
   3.741+	    do (iprintln (print-usage o) 4 stream)))
   3.742+    ;; (terpri stream)
   3.743+    (iprintln "commands:" 2 stream)
   3.744+    (unless (null cmds)
   3.745+      (loop for c across cmds
   3.746+	    do (iprintln (print-usage c) 4 stream)))))
   3.747+
   3.748+(defmethod cli-equal :before ((a cli) (b cli))
   3.749+  "Return T if A is the same cli object as B.
   3.750+
   3.751+Currently this function is intended only for instances of the CLI
   3.752+class and is used as a specialized EQL for DEFINE-CONSTANT."
   3.753+  (with-slots (version) a
   3.754+    (with-slots ((bv version)) b
   3.755+      (string= version bv))))
   3.756+
   3.757+;; same as cli-cmd method, default is to compile though
   3.758+(defmethod parse-args ((self cli) (args list) &key (compile t))
   3.759+  "Parse list of string arguments ARGS and return the updated object SELF."
   3.760+  (with-slots (opts cmds) self
   3.761+    (let ((args (if compile (proc-args self args) args)))
   3.762+      (install-ast self args))))
   3.763+
   3.764+(declaim (inline debug-opts))
   3.765+(defun debug-opts (cli)
   3.766+  (let ((o (active-opts cli))
   3.767+	(a (cli-cmd-args cli))
   3.768+	(c (active-cmds cli)))
   3.769+    (debug! (cli-cwd cli) o a c)))
   3.770+
   3.771+(declaim (inline solop))
   3.772+(defun solop (self)
   3.773+  (and (= 0 (length (active-opts self t)) (length (active-cmds self)))))
   3.774+
   3.775+(defmethod do-cmd ((self cli))
   3.776+  (if (solop self)
   3.777+      (call-cmd self (cli-cmd-args self) (cli-opts self))
   3.778+      (progn
   3.779+	(loop for o across (active-opts self t)
   3.780+	      do (do-opt o))
   3.781+	(loop for c across (active-cmds self)
   3.782+	      do (do-cmd c)))))
   3.783+
   3.784+(provide :cli)
     4.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2+++ b/lisp/lib/rdb.asd	Sun Oct 15 03:20:41 2023 -0400
     4.3@@ -0,0 +1,20 @@
     4.4+;;; rdb.asd --- thin RocksDB ORM
     4.5+(defsystem "rdb"
     4.6+  :version "0.1.0"
     4.7+  :license (:file "LICENSE")
     4.8+  :maintainer "ellis <ellis@rwest.io>"
     4.9+  :homepage "https://nas-t.net"
    4.10+  :bug-tracker "https://lab.rwest.io/comp/core/issues"
    4.11+  :depends-on (:macs :rocksdb)
    4.12+  :in-order-to ((test-op (test-op "rdb/tests")))
    4.13+  :components ((:file "rdb/rdb")))
    4.14+
    4.15+(defsystem "rdb/tests"
    4.16+  :version "0.1.0"
    4.17+  :license (:file "LICENSE")
    4.18+  :maintainer "ellis <ellis@rwest.io>"
    4.19+  :homepage "https://nas-t.net"
    4.20+  :bug-tracker "https://lab.rwest.io/comp/core/issues"
    4.21+  :depends-on (:rdb :rt)
    4.22+  :components ((:file "rdb/tests"))
    4.23+  :perform (test-op (op c) (uiop:symbol-call '#:rt '#:do-tests :rdb)))
     5.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2+++ b/lisp/lib/rdb/rdb.lisp	Sun Oct 15 03:20:41 2023 -0400
     5.3@@ -0,0 +1,204 @@
     5.4+;;; rdb.lisp --- High-level RocksDB API
     5.5+
     5.6+;; a thin ORM for working with RocksDB storage. 
     5.7+
     5.8+;; Low-level bindings are in rocksdb.lisp.
     5.9+
    5.10+;; Commentary:
    5.11+
    5.12+;; Code:
    5.13+(pkg:defpkg :rdb
    5.14+  (:use :cl :alien :fu :rocksdb)
    5.15+  (:import-from :sb-ext :string-to-octets :octets-to-string)
    5.16+  (:reexport :rocksdb)
    5.17+  (:export 
    5.18+   ;; opts
    5.19+   :make-rdb-opts
    5.20+   :rdb-opts
    5.21+   :default-rdb-opts
    5.22+   ;; db
    5.23+   :open-db
    5.24+   :with-open-db 
    5.25+   ;; iter
    5.26+   :create-iter :with-iter
    5.27+   :iter-key :iter-key-str
    5.28+   :iter-val :iter-val-str
    5.29+   ;; err
    5.30+   :unable-to-open-db 
    5.31+   :unable-to-put-key-value-to-db 
    5.32+   :unable-to-get-value-to-db))
    5.33+
    5.34+(in-package :rdb)
    5.35+
    5.36+(defstruct rdb-opts
    5.37+  (create-if-missing nil :type boolean)
    5.38+  (total-threads 1 :type integer) ;; numcpus is default
    5.39+  (max-open-files 10000 :type integer)
    5.40+  (use-fsync nil :type boolean)
    5.41+  (disable-auto-compations nil :type boolean))
    5.42+
    5.43+;; unsafe
    5.44+(defun bind-rocksdb-opts% (opts)
    5.45+  (let ((o (rocksdb-options-create)))
    5.46+    (with-slots (create-if-missing total-threads) opts
    5.47+      (rocksdb-options-set-create-if-missing o create-if-missing)
    5.48+      (rocksdb-options-increase-parallelism o total-threads))
    5.49+    o))
    5.50+
    5.51+(defun default-rdb-opts () 
    5.52+  (make-rdb-opts
    5.53+   :create-if-missing t 
    5.54+   :total-threads 4))
    5.55+
    5.56+(defun default-rocksdb-options% ()
    5.57+  (bind-rocksdb-opts% (default-rdb-opts)))
    5.58+
    5.59+(defmacro with-open-db ((db-var db-path &optional opt) &body body)
    5.60+  `(let ((,db-var (open-db ,db-path ,opt)))
    5.61+     (unwind-protect (progn ,@body)
    5.62+       (rocksdb-close ,db-var))))
    5.63+
    5.64+(defmacro with-iter ((iter-var db &optional opt) &body body)
    5.65+  `(let ((,iter-var (create-iter ,db ,opt)))
    5.66+     (unwind-protect (progn ,@body)
    5.67+       (rocksdb-iter-destroy ,iter-var))))
    5.68+
    5.69+;;; Conditions
    5.70+(define-condition unable-to-open-db (error)
    5.71+  ((db-path :initarg :db-path
    5.72+            :reader db-path)
    5.73+   (error-message :initarg :error-message
    5.74+                  :reader error-message)))
    5.75+
    5.76+(defmethod print-object ((obj unable-to-open-db) stream)
    5.77+  (print-unreadable-object (obj stream :type t :identity t)
    5.78+    (format stream "error-message=~A" (error-message obj))))
    5.79+
    5.80+(define-condition unable-to-put-key-value-to-db (error)
    5.81+  ((db :initarg :db
    5.82+       :reader db)
    5.83+   (key :initarg :key
    5.84+        :reader key)
    5.85+   (val :initarg :val
    5.86+        :reader val)
    5.87+   (error-message :initarg :error-message
    5.88+                  :reader error-message)))
    5.89+
    5.90+(define-condition unable-to-get-value-to-db (error)
    5.91+  ((db :initarg :db
    5.92+       :reader db)
    5.93+   (key :initarg :key
    5.94+        :reader key)
    5.95+   (error-message :initarg :error-message
    5.96+                  :reader error-message)))
    5.97+
    5.98+;;; API
    5.99+(defun open-db (db-path &optional opts)
   5.100+  (let ((opts (if opts (bind-rocksdb-opts% opts) (default-rocksdb-options%))))
   5.101+    (with-alien ((e rocksdb-errptr))
   5.102+      (let* ((db-path (if (pathnamep db-path)
   5.103+                          (namestring db-path)
   5.104+                          db-path))
   5.105+             (db (rocksdb-open opts db-path e)))
   5.106+	(if (null-alien e)
   5.107+            db
   5.108+            (error 'unable-to-open-db
   5.109+                   :db-path db-path
   5.110+                   :error-message e))))))
   5.111+
   5.112+(defun put-kv (db key val &optional opts)
   5.113+  (let ((opts (or opts (rocksdb-writeoptions-create)))
   5.114+	(klen (length key))
   5.115+	(vlen (length val)))
   5.116+    (with-alien ((errptr rocksdb-errptr nil)
   5.117+		 (k (* char) (make-alien char klen))
   5.118+		 (v (* char) (make-alien char vlen)))
   5.119+      (loop for x across key
   5.120+	    for i from 0 below klen
   5.121+	    do (setf (deref k i) x))
   5.122+      (loop for y across val
   5.123+	    for i from 0 below vlen
   5.124+	    do (setf (deref v i) y))
   5.125+      (rocksdb-put db
   5.126+		   opts
   5.127+		   k
   5.128+		   klen
   5.129+		   v
   5.130+		   vlen
   5.131+		   errptr)
   5.132+      (unless (null-alien errptr)
   5.133+        (error 'unable-to-put-key-value-to-db
   5.134+                :db db
   5.135+                :key key
   5.136+                :val val
   5.137+                :error-message (alien-sap errptr))))))
   5.138+
   5.139+(defun put-kv-str (db key val &optional opt)
   5.140+  (let ((key-octets (string-to-octets key))
   5.141+        (val-octets (string-to-octets val)))
   5.142+    (put-kv db key-octets val-octets opt)))
   5.143+
   5.144+(defun get-kv (db key &optional opt)
   5.145+  (let ((opt (or opt (rocksdb-readoptions-create)))
   5.146+	(key (string-to-octets key))
   5.147+	(klen (length key)))
   5.148+    (with-alien ((vlen (* size-t))
   5.149+		 (errptr rocksdb-errptr nil)
   5.150+		 (k (* char) (make-alien char klen)))
   5.151+      (loop for x across key
   5.152+	    for i from 0 below klen
   5.153+	    do (setf (deref k i) x))
   5.154+
   5.155+      (let* ((val (rocksdb-get db
   5.156+			      opt
   5.157+			      k
   5.158+			      klen
   5.159+			      vlen
   5.160+			      errptr))
   5.161+	     (vlen (deref vlen)))
   5.162+	(unless (null-alien errptr)
   5.163+          (error 'unable-to-get-value-to-db
   5.164+		 :db db
   5.165+		 :key key
   5.166+		 :error-message (alien-sap errptr)))
   5.167+	;; helps if we know the vlen beforehand, would need a custom
   5.168+	;; C-side function probably.
   5.169+	(let ((v (make-array vlen :element-type 'unsigned-byte)))
   5.170+	  (loop for i from 0 below vlen
   5.171+		with x = (deref val i) 
   5.172+		do (setf (aref v i) x))
   5.173+	  (map 'vector #'code-char v))))))
   5.174+
   5.175+ (defun get-kv-str (db key &optional opt)
   5.176+   (let ((k (string-to-octets key)))
   5.177+     (let ((v (get-kv db k opt)))
   5.178+       (when v (print v)))))
   5.179+
   5.180+(defun create-iter (db &optional opt)
   5.181+  (unless opt
   5.182+    (setq opt (rocksdb-readoptions-create)))
   5.183+  (rocksdb-create-iterator db opt))
   5.184+
   5.185+(defun iter-key (iter)
   5.186+  (with-alien ((klen-ptr (* unsigned-int)))
   5.187+    (let* ((key-ptr (rocksdb-iter-key iter klen-ptr))
   5.188+           (klen (deref klen-ptr))
   5.189+           (k (make-array klen :element-type '(unsigned-byte 8))))
   5.190+      (loop for i from 0 below klen with x = (deref key-ptr i) do (setf (aref k i) x))
   5.191+      k)))
   5.192+
   5.193+(defun iter-key-str (iter)
   5.194+  (when-let ((k (iter-key iter)))
   5.195+    (octets-to-string k)))
   5.196+
   5.197+ (defun iter-val (iter)
   5.198+   (with-alien ((vlen-ptr (* unsigned-int)))
   5.199+     (let* ((val-ptr (rocksdb-iter-value iter vlen-ptr))
   5.200+            (vlen (deref vlen-ptr))
   5.201+            (v (make-array vlen :element-type '(unsigned-byte 8))))
   5.202+       (loop for i from 0 below vlen with x = (deref val-ptr i) do (setf (aref v i) x))
   5.203+       v)))
   5.204+
   5.205+ (defun iter-val-str (iter)
   5.206+   (when-let ((v (iter-val iter)))
   5.207+     (octets-to-string v)))
     6.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2+++ b/lisp/lib/rdb/tests.lisp	Sun Oct 15 03:20:41 2023 -0400
     6.3@@ -0,0 +1,5 @@
     6.4+(defpackage :rdb.tests
     6.5+  (:use :cl :rt :rdb))
     6.6+(in-package :rdb.tests)
     6.7+(defsuite :rdb)
     6.8+(in-suite :rdb)
     7.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2+++ b/lisp/lib/rt.asd	Sun Oct 15 03:20:41 2023 -0400
     7.3@@ -0,0 +1,9 @@
     7.4+(defsystem "rt"
     7.5+  :version "0.1.0"
     7.6+  :author "ellis <ellis@rwest.io>"
     7.7+  :description "regression test framework"
     7.8+  :bug-tracker "https://lab.rwest.io/ellis/macs/issues"
     7.9+  :source-control (:hg "https://lab.rwest.io/ellis/macs")
    7.10+  :depends-on (:macs :sxp)
    7.11+  :in-order-to ((test-op (test-op "macs/tests")))
    7.12+  :components ((:file "rt")))
     8.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2+++ b/lisp/lib/rt/rt.lisp	Sun Oct 15 03:20:41 2023 -0400
     8.3@@ -0,0 +1,665 @@
     8.4+;;; rt.lisp --- regression testing
     8.5+
     8.6+;; Regression Testing framework. inspired by PCL, the original CMUCL
     8.7+;; code, and the SBCL port.
     8.8+
     8.9+;;; Commentary:
    8.10+
    8.11+;; - :rt https://www.merl.com/publications/docs/TR91-04.pdf Chapter 1
    8.12+;; - :com.gigamonkeys.test https://github.com/gigamonkey/monkeylib-test-framework
    8.13+;; - :sb-rt https://github.com/sbcl/sbcl/blob/master/contrib/sb-rt/rt.lisp
    8.14+
    8.15+;; This package is intended to provide a modernized Lisp testing
    8.16+;; library with features found in some of the test frameworks listed
    8.17+;; below.
    8.18+
    8.19+;; - :it.bese.fiveam https://github.com/lispci/fiveam
    8.20+;; - :try https://github.com/melisgl/try
    8.21+;; - :rove https://github.com/fukamachi/rove
    8.22+
    8.23+;;; TODO:
    8.24+#|
    8.25+
    8.26+- [ ] benchmark support: do-bench, test-count, 
    8.27+
    8.28+- [ ] fixtures api
    8.29+
    8.30+- [ ] profiling 
    8.31+|#
    8.32+;;; Code:
    8.33+#+x86-64
    8.34+(eval-when (:compile-toplevel :load-toplevel :execute)
    8.35+  (require 'sb-sprof))
    8.36+
    8.37+(pkg:defpkg :rt
    8.38+  (:use
    8.39+   :cl :sxp
    8.40+   :sym :list :cond :readtables :fu :fmt :log :ana :pan :sb-aprof
    8.41+   #+x86-64 :sb-sprof)
    8.42+  (:nicknames :rt)
    8.43+  (:export
    8.44+   :*default-test-opts*
    8.45+   :*compile-tests*
    8.46+   :*catch-test-errors*
    8.47+   :*test-suffix*
    8.48+   :*default-test-suite-name*
    8.49+   :*test-suite*
    8.50+   :*test-suite-list*
    8.51+   ;;  TODO 2023-09-04: :*test-profiler-list* not yet
    8.52+   :*testing*
    8.53+   :test-suite-designator
    8.54+   :check-suite-designator
    8.55+   :make-test
    8.56+   :make-suite
    8.57+   :test-name=
    8.58+   :do-test
    8.59+   :do-tests
    8.60+   :continue-testing
    8.61+   :with-test-env
    8.62+   :ensure-suite
    8.63+   :test-fixture
    8.64+   :fixture-prototype
    8.65+   :make-fixture-prototype
    8.66+   :make-fixture
    8.67+   :with-fixture
    8.68+   :test-result
    8.69+   :test-pass-p
    8.70+   :test-fail-p
    8.71+   :test-skip-p
    8.72+   :test-failed
    8.73+   :fail!
    8.74+   :is
    8.75+   :signals
    8.76+   :deftest
    8.77+   :defsuite
    8.78+   :in-suite
    8.79+   :eval-test
    8.80+   :compile-test
    8.81+   :locked-tests
    8.82+   :push-test
    8.83+   :pop-test
    8.84+   :delete-test
    8.85+   :find-test
    8.86+   :do-suite
    8.87+   :test-object
    8.88+   :test
    8.89+   :test-fixture
    8.90+   :test-suite
    8.91+   :test-name
    8.92+   :tests
    8.93+   :test-form
    8.94+   :test-results))
    8.95+
    8.96+(in-package :rt)
    8.97+(in-readtable *macs-readtable*)
    8.98+
    8.99+;;; Vars
   8.100+(defvar *default-test-opts* '(optimize sb-c::instrument-consing))
   8.101+(defvar *compile-tests* t
   8.102+  "When nil do not compile tests. With a value of t, tests are compiled
   8.103+with default optimizations else the value is used to configure
   8.104+compiler optimizations.")
   8.105+(defvar *catch-test-errors* t "When non-nil, cause errors in a test to be caught.")
   8.106+(defvar *test-suffix* "-test" "A suffix to append to every `test' defined with `deftest'.")
   8.107+(defvar *test-suite-list* nil "List of available `test-suite' objects.")
   8.108+(defvar *test-suite* nil "A 'test-suite-designator' which identifies the current `test-suite'.")
   8.109+(eval-when (:compile-toplevel :load-toplevel :execute)
   8.110+  (defvar *default-test-suite-name* "default"))
   8.111+(declaim (type (or stream boolean string) *test-input*))
   8.112+(defvar *test-input* nil "When non-nil, specifies an input stream or buffer for `*testing*'.")
   8.113+(defvar *default-bench-count* 100 "Default number of iterations to repeat a bench test for. This value is
   8.114+used when the slot value of :BENCH is t.")
   8.115+(defvar *testing* nil "Testing state var.")
   8.116+
   8.117+;;; Utils
   8.118+(eval-when (:compile-toplevel :load-toplevel :execute)
   8.119+  (defun make-test (&rest slots)
   8.120+    (apply #'make-instance 'test slots))
   8.121+  (defun make-suite (&rest slots)
   8.122+    (apply #'make-instance 'test-suite slots)))
   8.123+
   8.124+;; TODO 2023-09-04: optimize
   8.125+(declaim (inline do-tests))
   8.126+(defun do-tests (&optional (suite *test-suite*) (output *standard-output*))
   8.127+  (if (pathnamep output)
   8.128+      (with-open-file (stream output :direction :output)
   8.129+	(do-suite (ensure-suite suite) :stream stream))
   8.130+      (do-suite (ensure-suite suite) :stream output)))
   8.131+
   8.132+;; this assumes that *test-suite* is re-initialized correctly to the
   8.133+;; correct test-suite object.
   8.134+(defun continue-testing ()
   8.135+  (if-let ((test *testing*))
   8.136+    (throw '%in-test test)
   8.137+    (do-suite *test-suite*)))
   8.138+
   8.139+;; NOTE 2023-09-01: `pushnew' does not return an indication of whether
   8.140+;; place is changed - it returns place. This is functionally sound but
   8.141+;; means that if we want to do something else in the event that place
   8.142+;; is unchanged, we run into some friction,
   8.143+;; https://stackoverflow.com/questions/56228832/adapting-common-lisp-pushnew-to-return-success-failure
   8.144+(defun spush (item lst &key (test #'equal))
   8.145+  "Substituting `push'"
   8.146+  (declare (type function test))
   8.147+  (cond
   8.148+    ((null lst) (push item lst))
   8.149+    ((list lst)
   8.150+     (if-let ((found (member item lst
   8.151+			     :test test)))
   8.152+       (progn
   8.153+	 (rplaca found item)
   8.154+	 lst)
   8.155+       (push item lst)))
   8.156+    #|(or nil '(t (cons item lst)))|#))
   8.157+
   8.158+;; FIX 2023-08-31: spush, replace with `add-test' method.
   8.159+;; (declaim (inline normalize-test-name))
   8.160+(defun normalize-test-name (a)
   8.161+  "Return the normalized `test-suite-designator' of A."
   8.162+  (etypecase a
   8.163+    (string a)
   8.164+    (symbol (symbol-name a))
   8.165+    (test-object (test-name a))
   8.166+    (t (format nil "~A" a))))
   8.167+
   8.168+(defun test-name= (a b)
   8.169+  "Return t if A and B are similar `test-suite-designator's."
   8.170+  (let ((a (normalize-test-name a))
   8.171+	(b (normalize-test-name b)))
   8.172+    (string= a b)))
   8.173+
   8.174+;; (declaim (inline assert-suite ensure-suite))
   8.175+(defun ensure-suite (name)
   8.176+  (if-let ((ok (member name *test-suite-list* :test #'test-name=)))
   8.177+    (car ok)
   8.178+    (when (or (eq name t) (null name)) (make-suite :name *default-test-suite-name*))))
   8.179+
   8.180+(defun check-suite-designator (suite) (check-type suite test-suite-designator))
   8.181+
   8.182+(defun assert-suite (name)
   8.183+  (check-suite-designator name)
   8.184+  (assert (ensure-suite name)))
   8.185+
   8.186+(declaim (inline test-opt-key-p test-opt-valid-p))
   8.187+(defun test-opt-key-p (k)
   8.188+  "Test if K is a `test-opt-key'."
   8.189+  (member k '(:profile :save :stream)))
   8.190+
   8.191+(defun test-opt-valid-p (f)
   8.192+  "Test if F is a valid `test-opt' form. If so, return F else nil."
   8.193+  (when (test-opt-key-p (car f))
   8.194+    f))
   8.195+
   8.196+;;; Conditions
   8.197+(define-condition test-failed (error)
   8.198+  ((reason :accessor fail-reason :initarg :reason :initform "unknown")
   8.199+   (name :accessor fail-name :initarg :name)
   8.200+   (form :accessor fail-form :initarg :form))
   8.201+  (:documentation "Signaled when a test fails.")
   8.202+  (:report (lambda (c s)
   8.203+	     (format s "The following expression failed: ~S~%~A."
   8.204+		     (fail-form c)
   8.205+		     (fail-reason c)))))
   8.206+
   8.207+;;; Protocol
   8.208+(defgeneric eval-test (self)
   8.209+  (:documentation "Eval a `test'."))
   8.210+
   8.211+(defgeneric compile-test (self &key &allow-other-keys)
   8.212+  (:documentation "Compile a `test'."))
   8.213+
   8.214+(defgeneric locked-tests (self)
   8.215+  (:documentation "Return a list of locked tests in `test-suite' object SELF."))
   8.216+
   8.217+(defgeneric push-test (self place)
   8.218+  (:documentation
   8.219+   "Push `test' SELF to the value of slot ':tests' in `test-suite' object PLACE."))
   8.220+
   8.221+(defgeneric pop-test (self)
   8.222+  (:documentation
   8.223+   "Pop the first `test' from the slot-value of ':tests' in `test-suite' object SELF."))
   8.224+
   8.225+(defgeneric push-result (self place)
   8.226+  (:documentation
   8.227+   "Push object SELF to the value of slot ':results' in object PLACE."))
   8.228+
   8.229+(defgeneric pop-result (self)
   8.230+  (:documentation
   8.231+   "Pop the first `test-result' from the slot-value of ':tests' from object SELF."))
   8.232+
   8.233+(defgeneric push-fixture (self place)
   8.234+  (:documentation
   8.235+   "Push object SELF to the value of slot ':results' in object PLACE."))
   8.236+
   8.237+(defgeneric delete-test (self &key &allow-other-keys)
   8.238+  (:documentation "Delete `test' object specified by `test-object' SELF and optional keys."))
   8.239+
   8.240+(defgeneric find-test (self name &key &allow-other-keys)
   8.241+  (:documentation "Find `test' object specified by name and optional keys."))
   8.242+
   8.243+(defgeneric do-test (self &optional test)
   8.244+  (:documentation
   8.245+   "Run `test' SELF, printing results to `*standard-output*'. The second
   8.246+argument is an optional fixture.
   8.247+
   8.248+SELF can also be a `test-suite', in which case the TESTS slot is
   8.249+queried for the value of TEST. If TEST is not provided, pops the car
   8.250+from TESTS."))
   8.251+
   8.252+(defgeneric do-suite (self &key &allow-other-keys)
   8.253+  (:documentation
   8.254+   "Perform actions on `test-suite' object SELF with optional keys."))
   8.255+
   8.256+;;;; Results
   8.257+(deftype result-tag ()
   8.258+  '(or (member :pass :fail :skip) null))
   8.259+
   8.260+(declaim (inline %make-test-result))
   8.261+(defstruct (test-result (:constructor %make-test-result)
   8.262+			(:conc-name  tr-))
   8.263+  (tag nil :type result-tag :read-only t)
   8.264+  (form nil :type form))
   8.265+
   8.266+(defun make-test-result (tag &optional form)
   8.267+  (%make-test-result :tag tag :form form))
   8.268+
   8.269+(defmethod test-pass-p ((res test-result))
   8.270+  (when (eq :pass (tr-tag res)) t))
   8.271+
   8.272+(defmethod test-fail-p ((res test-result))
   8.273+  (when (eq :fail (tr-tag res)) t))
   8.274+
   8.275+(defmethod test-skip-p ((res test-result))
   8.276+  (when (eq :skip (tr-tag res)) t))
   8.277+
   8.278+(defmethod print-object ((self test-result) stream)
   8.279+  (print-unreadable-object (self stream)
   8.280+    (format stream "~A ~A"
   8.281+	    (tr-tag self)
   8.282+	    (tr-form self))))
   8.283+
   8.284+;;; Objects
   8.285+(defclass test-object ()
   8.286+  ((name :initarg :name :initform (required-argument) :type string :accessor test-name)
   8.287+   #+nil (cached :initarg :cache :allocation :class :accessor test-cached-p :type boolean))
   8.288+  (:documentation "Super class for all test-related objects."))
   8.289+
   8.290+(defmethod print-object ((self test-object) stream)
   8.291+  "test"
   8.292+  (print-unreadable-object (self stream :type t :identity t)
   8.293+    (format stream "~A"
   8.294+	    (test-name self))))
   8.295+
   8.296+;;;; Tests
   8.297+;; HACK 2023-08-31: inherit sxp?
   8.298+
   8.299+(defclass test (test-object)
   8.300+  ((fn :type symbol :accessor test-fn)
   8.301+   (bench :type (or boolean fixnum) :accessor test-bench :initform nil :initarg :bench)
   8.302+   (profile :type list :accessor test-profile :initform nil :initarg :profile)
   8.303+   (args :type list :accessor test-args :initform nil :initarg :args)
   8.304+   (decl :type list :accessor test-decl :initform nil :initarg :decl)
   8.305+   (form :initarg :form :initform nil :type function-lambda-expression :accessor test-form)
   8.306+   (doc :initarg :doc :type string :accessor test-doc)
   8.307+   (lock :initarg :lock :type boolean :accessor test-lock-p)
   8.308+   (persist :initarg :persist :initform nil :type boolean :accessor test-persist-p)
   8.309+   (results :initarg :results :type (array test-result) :accessor test-results))
   8.310+  (:documentation "Test class typically made with `deftest'."))
   8.311+
   8.312+(defmethod test-bench-p ((self test))
   8.313+  (when (test-bench self) t))
   8.314+
   8.315+(defmethod get-bench-count ((self test))
   8.316+  (when-let ((v (test-bench self)))
   8.317+    (cond
   8.318+      ((typep v 'fixnum) v)
   8.319+      ((eq v t) *default-bench-count*)
   8.320+      ;; unknown value
   8.321+      (t nil))))
   8.322+
   8.323+(defmethod initialize-instance ((self test) &key name)
   8.324+  ;; (debug! "building test" name)
   8.325+  (setf (test-fn self)
   8.326+	(make-symbol
   8.327+	 (format nil "~A~A"
   8.328+		 name
   8.329+		 (gensym *test-suffix*))))
   8.330+  (setf (test-lock-p self) t)
   8.331+  ;; TODO 2023-09-21: we should count how many checks are in the :form
   8.332+  ;; slot and infer the array dimensions.
   8.333+  (setf (test-results self) (make-array 0 :element-type 'test-result))
   8.334+  (call-next-method))
   8.335+
   8.336+(defmethod print-object ((self test) stream)
   8.337+  (print-unreadable-object (self stream :type t :identity t)
   8.338+    (format stream "~A :fn ~A :args ~A :persist ~A"
   8.339+	    (test-name self)
   8.340+	    (test-fn self)
   8.341+	    (test-args self)
   8.342+	    (test-persist-p self))))
   8.343+
   8.344+;; TODO 2023-09-01: use sxp?
   8.345+;; (defun validate-form (form))
   8.346+
   8.347+(defmethod push-result ((self test-result) (place test))
   8.348+  (with-slots (results) place
   8.349+    (push self results)))
   8.350+
   8.351+(defmethod pop-result ((self test))
   8.352+  (pop (test-results self)))
   8.353+
   8.354+(defmethod eval-test ((self test))
   8.355+  `(progn ,@(test-form self)))
   8.356+
   8.357+(defmethod compile-test ((self test) &key declare &allow-other-keys)
   8.358+  (compile
   8.359+   (test-fn self)
   8.360+   `(lambda ()
   8.361+      ,@(when declare `((declare ,declare)))
   8.362+      ,@(test-form self))))
   8.363+
   8.364+(defun fail! (form &optional fmt &rest args)
   8.365+  (let ((reason (and fmt (apply #'format nil fmt args))))
   8.366+    (with-simple-restart (ignore-fail "Continue testing.")
   8.367+      (error 'test-failed :reason reason :form form))))
   8.368+
   8.369+(defmacro with-test-env (self &body body)
   8.370+  `(catch '%in-test
   8.371+     (setf (test-lock-p ,self) t)
   8.372+     (let* ((*testing* ,self)
   8.373+	    (bail nil)
   8.374+	    r)
   8.375+       (block bail
   8.376+	 ,@body
   8.377+	 (setf (test-lock-p ,self) bail))
   8.378+       r)))
   8.379+
   8.380+(defmethod do-test ((self test) &optional fx)
   8.381+  (declare (ignorable fx))
   8.382+  (with-test-env self
   8.383+    (debug! "running test: " *testing*)
   8.384+    (flet ((%do ()
   8.385+	     (if-let ((opt *compile-tests*))
   8.386+	       ;; RESEARCH 2023-08-31: with-compilation-unit?
   8.387+	       (progn 
   8.388+		 (when (eq opt t) (setq opt *default-test-opts*))
   8.389+		 ;; TODO 2023-09-21: handle failures here
   8.390+		 (funcall (compile-test self :declare opt))
   8.391+		 (setf r (make-test-result :pass (test-fn self))))
   8.392+	       (progn
   8.393+		 (eval-test self)
   8.394+		 (setf r (make-test-result :pass (test-name self)))))))
   8.395+      (if *catch-test-errors*
   8.396+	  (handler-bind
   8.397+	      ((style-warning #'muffle-warning)
   8.398+	       (error 
   8.399+		 #'(lambda (c)
   8.400+		     (setf bail t)
   8.401+		     (setf r (make-test-result :fail c))
   8.402+		     (return-from bail r))))
   8.403+	    (%do))
   8.404+	  (%do)))))
   8.405+
   8.406+(defmacro bench (iter &body body)
   8.407+  `(loop for i from 1 to ,iter
   8.408+	 do ,@body))
   8.409+
   8.410+(defmethod do-bench ((self test) &optional fx)
   8.411+  (declare (ignorable fx))
   8.412+  (with-test-env self
   8.413+    (flet ((%do ()
   8.414+	     (if-let ((opt *compile-tests*))
   8.415+	       (progn 
   8.416+		 (when (eq opt t) (setq opt *default-test-opts*))
   8.417+		 ;; TODO 2023-09-21: handle failures here
   8.418+		 (let ((fn (compile-test self :declare opt)))
   8.419+		   (bench (test-bench self) (funcall fn)))
   8.420+		 (setf r (make-test-result :pass (test-fn self))))
   8.421+	       (progn
   8.422+		 (bench (test-bench self) (eval-test self))
   8.423+		 (setf r (make-test-result :pass (test-name self)))))))
   8.424+      (if *catch-test-errors*
   8.425+	  (handler-bind
   8.426+	      ((style-warning #'muffle-warning)
   8.427+	       (error 
   8.428+		 #'(lambda (c)
   8.429+		     (setf bail t)
   8.430+		     (setf r (make-test-result :fail c))
   8.431+		     (return-from bail r))))
   8.432+	    (%do))
   8.433+	  (%do)))))
   8.434+
   8.435+;;;; Fixtures
   8.436+
   8.437+;; Our fixtures are just closures - with a pandoric environment. You
   8.438+;; might call it a domain-specific object protocol.
   8.439+
   8.440+;; You can build fixtures inside a test or use the push-fixture
   8.441+;; method on a `test-suite' object.
   8.442+
   8.443+(deftype fixture () 'form)
   8.444+
   8.445+(declaim (inline %make-fixture-prototype))
   8.446+(defstruct (fixture-prototype (:constructor %make-fixture-prototype)
   8.447+			      (:conc-name fxp))
   8.448+  (kind :empty :type keyword)
   8.449+  (form nil :type form))
   8.450+
   8.451+(defun make-fixture-prototype (kind form)
   8.452+  (%make-fixture-prototype :kind kind :form form))
   8.453+
   8.454+(defmacro make-fixture (letargs &body ds)
   8.455+  (let ((letargs (let-binding-transform letargs)))
   8.456+    `(let (,@letargs)
   8.457+       (dlambda ,@ds))))
   8.458+
   8.459+(defmacro with-fixture ((var fx) &body body)
   8.460+  `(let ((,var ,fx))
   8.461+     ,@body))
   8.462+
   8.463+;;;; Suites
   8.464+(defclass test-suite (test-object)
   8.465+  ((tests :initarg :set :initform nil :type list :accessor tests
   8.466+	  :documentation "test-suite tests")
   8.467+   (results :initarg :results :initform nil :type list :accessor test-results
   8.468+	    :documentation "test-suite results")
   8.469+   (stream :initarg :stream :initform *standard-output* :type stream :accessor test-stream)
   8.470+   (fixtures :initarg :fixtures :initform nil :type list :accessor test-fixtures))
   8.471+  (:documentation "A class for collections of related `test' objects."))
   8.472+
   8.473+(defmethod print-object ((self test-suite) stream)
   8.474+  (print-unreadable-object (self stream :type t :identity t)
   8.475+    (format stream "~A [~d+~d:~d:~d:~d]"
   8.476+	    (test-name self)
   8.477+	    (count t (map-tests self (lambda (x) (not (test-bench-p x)))))
   8.478+	    (count t (map-tests self #'test-bench-p))
   8.479+	    (count t (map-tests self #'test-lock-p))
   8.480+	    (count t (map-tests self #'test-persist-p))
   8.481+	    (length (test-results self)))))
   8.482+
   8.483+;; (defmethod reinitialize-instance ((self test-suite) &rest initargs &key &allow-other-keys))
   8.484+
   8.485+(deftype test-suite-designator ()
   8.486+  "Either nil, a symbol, a string, or a `test-suite' object."
   8.487+  '(or null symbol string test-suite test keyword))
   8.488+
   8.489+(defmethod map-tests ((self test-suite) function)
   8.490+  (mapcar function (tests self)))
   8.491+
   8.492+(defmethod push-test ((self test) (place test-suite))
   8.493+  (push self (tests place)))
   8.494+
   8.495+(defmethod pop-test ((self test-suite))
   8.496+  (pop (tests self)))
   8.497+
   8.498+(defmethod push-result ((self test-result) (place test-suite))
   8.499+  (with-slots (results) place
   8.500+    (push self results)))
   8.501+
   8.502+(defmethod pop-result ((self test-suite))
   8.503+  (pop (test-results self)))
   8.504+
   8.505+(defmethod find-test ((self test-suite) name &key (test #'test-name=))
   8.506+  (declare (type (or string symbol) name)
   8.507+	   (type function test))
   8.508+  (find name (the list (tests self)) :test test))
   8.509+
   8.510+(defmethod do-test ((self test-suite) &optional test)
   8.511+  (push-result 
   8.512+   (if test
   8.513+       (do-test (find-test self (test-name test)))
   8.514+       (do-test (pop-test self)))
   8.515+   self))
   8.516+
   8.517+;; HACK 2023-09-01: find better method of declaring failures from
   8.518+;; within the body of `deftest'.
   8.519+(defmethod do-suite ((self test-suite) &key stream)
   8.520+  (when stream (setf (test-stream self) stream))
   8.521+  (with-slots (name stream) self
   8.522+    (format stream "in suite ~x with ~A/~A tests:~%"
   8.523+	    name
   8.524+	    (count t (tests self)
   8.525+		   :key (lambda (x) (or (test-lock-p x) (test-persist-p x))))
   8.526+	    (length (tests self)))
   8.527+    ;; loop over each test, calling `do-test' if locked or persistent
   8.528+    (map-tests self 
   8.529+	       (lambda (x)
   8.530+		 (when (or (test-lock-p x) (test-persist-p x))
   8.531+		   (let ((res (do-test x)))
   8.532+		     (push-result res self)
   8.533+		     (format stream "~@[~<~%~:;~:@(~S~) ~>~]~%" res)))))
   8.534+    ;; compare locked vs expected
   8.535+    (let ((locked (remove-if #'null (map-tests self (lambda (x) (when (test-lock-p x) x)))))
   8.536+	  (fails
   8.537+	    ;; collect if locked test not expected
   8.538+	    (loop for r in (test-results self)
   8.539+		  unless (test-pass-p r)
   8.540+		    collect r)))
   8.541+      (if (null locked)
   8.542+	  (format stream "~&No tests failed.~%")
   8.543+	  (progn
   8.544+	    ;;  RESEARCH 2023-09-04: print fails ??
   8.545+	    (format stream "~&~A out of ~A ~
   8.546+                   total tests failed: ~
   8.547+                   ~:@(~{~<~%   ~1:;~S~>~
   8.548+                         ~^, ~}~)."
   8.549+		    (length locked)
   8.550+		    (length (tests self))
   8.551+		    locked)
   8.552+	    (unless (null fails)
   8.553+	      (format stream "~&~A unexpected failures: ~
   8.554+                   ~:@(~{~<~%   ~1:;~S~>~
   8.555+                         ~^, ~}~)."
   8.556+		      (length fails)
   8.557+		      fails))))
   8.558+      ;; close stream
   8.559+      (finish-output stream)
   8.560+      ;; return values (PASS? LOCKED)
   8.561+      (values (not fails) locked))))
   8.562+
   8.563+;;; Checks
   8.564+(flet ((%test (val form)
   8.565+	 (let ((r 
   8.566+		 (if val 
   8.567+		     (make-test-result :pass form)
   8.568+		     (make-test-result :fail form))))
   8.569+	   (debug! r)
   8.570+	   r)))
   8.571+  (defmacro is (test &rest args)
   8.572+    "The DWIM Check.
   8.573+
   8.574+(is (= 1 1) :test 100) ;=> #S(TEST-RESULT :TAG :PASS :FORM (= 1 1))
   8.575+If TEST returns a truthy value, return a PASS test-result, else return
   8.576+a FAIL. The TEST is parameterized by ARGS which is a plist or nil.
   8.577+
   8.578+If ARGS is nil, TEST is bound to to the RESULT slot of the test-result
   8.579+and evaluated 'as-is'.
   8.580+
   8.581+(nyi!)
   8.582+ARGS may contain the following keywords followed by a corresponding
   8.583+value:
   8.584+
   8.585+:EXPECTED
   8.586+
   8.587+:TIMEOUT
   8.588+
   8.589+:THEN
   8.590+
   8.591+All other values are treated as let bindings.
   8.592+"
   8.593+    (with-gensyms (form)
   8.594+      `(if ,(null args)
   8.595+	   (if *testing* 
   8.596+	       (push-result (funcall ,#'%test ,test ',test) *testing*)
   8.597+	       (funcall ,#'%test ,test ',test))
   8.598+	   (macrolet ((,form (test) `(let ,,(group args 2) ,,test)))
   8.599+	     ;; TODO 2023-09-21: does this work...
   8.600+	     (if *testing*
   8.601+		 (push-result (funcall ,#'%test (,form ,test) ',test) *testing*)
   8.602+		 (funcall ,#'%test (,form ,test) ',test)))))))
   8.603+
   8.604+(defmacro signals (condition-spec &body body)
   8.605+  "Generates a passing TEST-RESULT if body signals a condition of type
   8.606+CONDITION-SPEC. BODY is evaluated in a block named NIL, CONDITION-SPEC
   8.607+is not evaluated."
   8.608+  (let ((block-name (gensym)))
   8.609+    (destructuring-bind (condition &optional reason-control &rest reason-args)
   8.610+        (ensure-list condition-spec)
   8.611+      `(block ,block-name
   8.612+         (handler-bind ((,condition (lambda (c)
   8.613+                                      ;; ok, body threw condition
   8.614+				      ;; TODO 2023-09-05: result collectors
   8.615+                                      ;; (add-result 'test-passed
   8.616+                                      ;;            :test-expr ',condition)
   8.617+                                      (return-from ,block-name (make-test-result :pass ',body)))))
   8.618+           (block nil
   8.619+             ,@body))
   8.620+         (fail!
   8.621+          ',condition
   8.622+          ,@(if reason-control
   8.623+                `(,reason-control ,@reason-args)
   8.624+                `("Failed to signal a ~S" ',condition)))
   8.625+         (return-from ,block-name nil)))))
   8.626+
   8.627+;;; Macros
   8.628+(defmacro deftest (name props &body body)
   8.629+  "Build a test with NAME, parameterized by LAMBDA-LIST and with a test form of BODY."
   8.630+  (destructuring-bind (pr doc dec fn)
   8.631+      (multiple-value-bind (forms dec doc)
   8.632+	  ;; parse body with docstring allowed
   8.633+	  (sb-int:parse-body
   8.634+	   (or body) t)
   8.635+	`(,props ',doc ',dec ',forms))
   8.636+    ;; TODO 2023-09-21: parse plist
   8.637+    `(let ((obj (make-test
   8.638+		 :name ',(format nil "~A" name)
   8.639+		 ;; note: we could leave these unbound if we want,
   8.640+		 ;; personal preference
   8.641+		 :form ,fn
   8.642+		 ,@(when-let ((v (getf pr :persist))) `(:persist ,v))
   8.643+		 ,@(when-let ((v (getf pr :args))) `(:args ,v))
   8.644+		 ,@(when-let ((v (getf pr :bench))) `(:bench ,v))
   8.645+		 ,@(when-let ((v (getf pr :profile))) `(:profile ,v))
   8.646+		 ,@(when doc `(:doc ,doc))
   8.647+		 ,@(when dec `(:decl ,dec)))))
   8.648+       (push-test obj *test-suite*)
   8.649+       obj)))
   8.650+
   8.651+(defmacro defsuite (suite-name &rest props)
   8.652+  "Define a `test-suite' with provided keys. The object returned can be
   8.653+enabled using the `in-suite' macro, similiar to the `defpackage' API."
   8.654+  (check-type suite-name (or symbol string))
   8.655+  `(eval-when (:compile-toplevel :load-toplevel :execute)
   8.656+     (let ((obj (make-suite
   8.657+		 :name (format nil "~A" ',suite-name)
   8.658+		 ,@(when-let ((v (getf props :stream))) `(:stream ,v)))))
   8.659+       (setq *test-suite-list* (spush obj *test-suite-list* :test #'test-name=))
   8.660+       obj)))
   8.661+
   8.662+(defmacro in-suite (name)
   8.663+  "Set `*test-suite*' to the `test-suite' referred to by symbol
   8.664+NAME. Return the `test-suite'."
   8.665+  (assert-suite name)
   8.666+  `(setf *test-suite* (ensure-suite ',name)))
   8.667+
   8.668+(provide :rt)
     9.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2+++ b/lisp/lib/sxp.asd	Sun Oct 15 03:20:41 2023 -0400
     9.3@@ -0,0 +1,27 @@
     9.4+;;; sxp.asd
     9.5+(defsystem "sxp"
     9.6+  :version "0.1.0"
     9.7+  :author "ellis <ellis@rwest.io>"
     9.8+  :maintainer "ellis <ellis@rwest.io>"
     9.9+  :depends-on ("macs")
    9.10+  :description "S-eXPressions"
    9.11+  :homepage "https://rwest.io/sxp"
    9.12+  :bug-tracker "https://lab.rwest.io/comp/sxp/issues"
    9.13+  :source-control "https://lab.rwest.io/comp/sxp"
    9.14+  :license "WTF"
    9.15+  :in-order-to ((test-op (test-op :sxp/tests)))
    9.16+  :components ((:file "sxp/sxp")))
    9.17+
    9.18+(defmethod perform :after ((op load-op) (c (eql (find-system :sxp))))
    9.19+  (pushnew :sxp *features*))
    9.20+
    9.21+(defsystem "sxp/tests"
    9.22+  :depends-on ("sxp" "rt" "uiop")
    9.23+  :components ((:file "sxp/tests"))
    9.24+  :perform (test-op (op c)
    9.25+		    (uiop:symbol-call '#:rt '#:do-tests)))
    9.26+
    9.27+(defsystem "sxp/bench"
    9.28+  :depends-on ("sxp" "uiop" "sb-sprof" "flamegraph")
    9.29+  :components ((:file "sxp/bench"))
    9.30+  :perform (test-op (op c) (uiop:symbol-call '#:sxp-bench '#:run-bench)))
    10.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2+++ b/lisp/lib/sxp/bench.lisp	Sun Oct 15 03:20:41 2023 -0400
    10.3@@ -0,0 +1,49 @@
    10.4+(require :sb-sprof)
    10.5+(defpackage :sxp-bench
    10.6+  (:use :cl :sxp :sb-ext :sb-unix)
    10.7+  (:export :run-bench :*bench-input-file* :*bench-input-string* :*bench-input-object*
    10.8+	   :*bench-output-directory* :*bench-iterations* :*bench-report-file* ;:*bench-flamegraph-file*
    10.9+	   ))
   10.10+(in-package :sxp-bench)
   10.11+(declaim
   10.12+ (type (or string pathname) *bench-input-file* *bench-output-directory* *bench-report-file*)
   10.13+ (type string *bench-input-string*)
   10.14+ (type sxp *bench-input-object*)
   10.15+ (type integer *bench-iterations*))
   10.16+(defparameter *bench-input-file* "tests.sxp")
   10.17+(defparameter *bench-input-string* (uiop:read-file-string *bench-input-file*))
   10.18+(defparameter *bench-input-object* (make-instance 'sxp))
   10.19+(read-sxp-string *bench-input-object* *bench-input-string*)
   10.20+
   10.21+(defparameter *bench-output-directory* "/tmp/sxp-bench")
   10.22+(defparameter *bench-iterations* 1000)
   10.23+(defparameter *bench-report-file* "bench.sxp")
   10.24+;; (defparameter *bench-flamegraph-file* "bench.stack")
   10.25+(defmacro bench (&body body)
   10.26+  `(loop for i from 1 to *bench-iterations*
   10.27+	 do ,@body))
   10.28+
   10.29+(defun rbench (fn input)
   10.30+  (let ((res))
   10.31+    (bench (call-with-timing (lambda (&rest x) (push (cons i x) res)) fn input))
   10.32+    (nreverse res)))
   10.33+
   10.34+(defun wbench (fn)
   10.35+  (let ((res))
   10.36+    (bench (let ((out (make-pathname :name (format nil "~d.sxp" i) :directory *bench-output-directory*)))
   10.37+	     (call-with-timing (lambda (&rest x) (push (cons i x) res)) fn *bench-input-object* out :if-exists :supersede)))
   10.38+    (nreverse res)))
   10.39+
   10.40+(defun run-bench (&optional rpt)
   10.41+  (when (probe-file *bench-output-directory*)
   10.42+    (sb-ext:delete-directory *bench-output-directory* :recursive t))
   10.43+  (sb-unix:unix-mkdir *bench-output-directory* #o777)
   10.44+  (let ((rres (sb-sprof:with-profiling (:sample-interval 0.001) (rbench #'sxp:read-sxp-file *bench-input-file*)))
   10.45+	(wres (sb-sprof:with-profiling (:sample-interval 0.001) (wbench #'sxp:write-sxp-file))))
   10.46+    (if rpt
   10.47+	(progn
   10.48+	  (format t "Writing output to ~s" *bench-report-file*)
   10.49+	  (uiop:with-output-file (out *bench-report-file* :if-exists :supersede :if-does-not-exist :create)
   10.50+	    (print `(,@rres ,@wres) out)))
   10.51+	(print (list rres wres))))
   10.52+  (terpri))
    11.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2+++ b/lisp/lib/sxp/sxp.lisp	Sun Oct 15 03:20:41 2023 -0400
    11.3@@ -0,0 +1,158 @@
    11.4+;;; sxp.lisp --- S-eXPressions
    11.5+
    11.6+;; sxp is a unified S-Expression data format
    11.7+
    11.8+;;; Code:
    11.9+(pkg:defpkg :sxp
   11.10+    (:use :cl :sb-mop :sym :fu)
   11.11+  (:import-from :uiop :read-file-forms :slurp-stream-forms :with-output-file)
   11.12+  ;; TODO: hot-patch readtables into sxp classes/parsers
   11.13+  (:import-from :readtables :defreadtable :in-readtable)
   11.14+  (:export
   11.15+   :sxp-fmt-designator
   11.16+   :form :formp :sxp-error :sxp-fmt-error :sxp-syntax-error :reader :writer :fmt
   11.17+   :wrap :wrap! :wrap-from-string! :unwrap :unwrap! :unwrap-or
   11.18+   :sxpp :build-ast :load-ast :ast
   11.19+   :define-macro :define-fmt :read-sxp-file :write-sxp-file
   11.20+   :read-sxp-string :write-sxp-string :read-sxp-stream :write-sxp-stream
   11.21+   :make-sxp :sxp :formp :form
   11.22+   :wrap-object :unwrap-object))
   11.23+
   11.24+(in-package :sxp)
   11.25+
   11.26+(defun formp (form)
   11.27+  (or (consp form) (atom form)))
   11.28+
   11.29+(deftype form ()
   11.30+  '(satisfies formp))
   11.31+
   11.32+;;; Conditions
   11.33+(define-condition sxp-error (error) ())
   11.34+
   11.35+(define-condition sxp-fmt-error (sxp-error)
   11.36+  ((format-control :initarg :format-control :reader format-control)
   11.37+   (format-arguments :initarg :format-arguments :reader format-arguments))
   11.38+  (:report (lambda (c s)
   11.39+             (apply 'format s (format-control c) (format-arguments c)))))
   11.40+
   11.41+(define-condition sxp-syntax-error (sxp-error) ())
   11.42+
   11.43+ ;;; Protocol
   11.44+(defgeneric wrap (self form))
   11.45+(defgeneric wrap! (self form))
   11.46+(defgeneric wrap-from-string! (self str))
   11.47+(defgeneric unwrap (self))
   11.48+(defgeneric unwrap! (self))
   11.49+(defgeneric unwrap-or (self lambda))
   11.50+(defgeneric sxpp (self form))
   11.51+
   11.52+(defgeneric write-sxp-stream (self stream &key pretty case))
   11.53+(defgeneric read-sxp-stream (self stream))
   11.54+
   11.55+(defgeneric build-ast (self &key &allow-other-keys)
   11.56+  (:documentation "build the sxp representation of SELF and store it in the :ast
   11.57+slot. The :ast slot is always ignored."))
   11.58+
   11.59+(defgeneric load-ast (self)
   11.60+  (:documentation "load the object SELF from the :ast slot."))
   11.61+
   11.62+;;; Objects
   11.63+(defclass sxp ()
   11.64+  ((ast :initarg :ast :type form :accessor ast))
   11.65+  (:documentation "Dynamic class representing a SXP form."))
   11.66+
   11.67+(defmethod wrap! ((self sxp) form) (setf (slot-value self 'ast) (ignore-errors form)))
   11.68+
   11.69+(defmethod wrap-from-string! ((self sxp) str) (setf (slot-value self 'ast) (ignore-errors (read str))))
   11.70+
   11.71+(defmethod wrap ((self sxp) form) (setf (slot-value self 'ast) form))
   11.72+
   11.73+(defmethod unwrap ((self sxp)) (slot-value self 'ast))
   11.74+
   11.75+(defmethod unwrap! ((self sxp)) (ignore-errors (slot-value self 'ast)))
   11.76+
   11.77+(defmethod unwrap-or ((self sxp) else-fn)
   11.78+  (if (slot-unbound 'sxp self 'ast)
   11.79+      (slot-value self 'ast)
   11.80+      (if (null (slot-value self 'ast))
   11.81+	  (funcall else-fn))))
   11.82+
   11.83+(defmethod write-sxp-stream ((self sxp) stream &key (pretty *print-pretty*) (case :downcase))
   11.84+  (write (ast self)
   11.85+	 :stream stream
   11.86+	 :pretty pretty
   11.87+	 :case case))
   11.88+
   11.89+(defmethod read-sxp-stream ((self sxp) stream)
   11.90+  (setf (ast self) (slurp-stream-forms stream :count nil)))
   11.91+
   11.92+;; (defsetf unwrap ) (defsetf wrap )
   11.93+
   11.94+;;; Functions
   11.95+(defun read-sxp-file (file)
   11.96+  (make-instance 'sxp :ast (read-file-forms file)))
   11.97+
   11.98+(defun write-sxp-file (sxp file &optional &key if-exists)
   11.99+  (with-output-file (out file) :if-exists if-exists
  11.100+    (write-sxp-stream sxp out)))
  11.101+
  11.102+(defun read-sxp-string (self str) (with-input-from-string (s str) (read-sxp-stream self s)))
  11.103+
  11.104+(defun write-sxp-string (sxp) 
  11.105+  (let ((ast (ast sxp)))
  11.106+    (if (> (length ast) 1)
  11.107+	(write-to-string ast)
  11.108+	(write-to-string (car ast)))))
  11.109+
  11.110+(defun make-sxp (&rest form) (make-instance 'sxp :ast form))
  11.111+
  11.112+(deftype sxp-fmt-designator () '(member :canonical :collapsed)) 
  11.113+
  11.114+(defun unwrap-object (obj &key (slots t) (methods nil)
  11.115+			    (indirect nil) (tag nil)
  11.116+			    (unboundp nil) (nullp nil)
  11.117+			    (exclude nil))
  11.118+  "Build and return a new `form' from OBJ by traversing the class
  11.119+definition. This differs from the generic function `unwrap' which
  11.120+always uses the ast slot as an internal buffer. We can also call this
  11.121+on any class instance (doesn't need to subclass `sxp').
  11.122+
  11.123+SLOTS specifies the slots to be included in the output. If the value
  11.124+is t, all slots are included. The ast slot is not included by default,
  11.125+but this behavior may change in future revisions.
  11.126+
  11.127+When INDIRECT is non-nil, also include methods which indirectly
  11.128+specialize on OBJ.
  11.129+
  11.130+When TAG is non-nil, return a cons where car is TAG and cdr is the
  11.131+output. If TAG is t, use the class-name symbol."
  11.132+  (declare (type standard-object obj)
  11.133+	   (type (or list boolean) slots)
  11.134+	   (type (or list boolean) methods)
  11.135+	   (type boolean indirect)
  11.136+	   (type list exclude))
  11.137+  (unless (or slots methods)
  11.138+    (error "Required one missing key arg: SLOTS or METHODS"))
  11.139+  (let* ((class (class-of obj))
  11.140+	(res (when tag (list (if (eq t tag) (class-name class) tag)))))
  11.141+    (block unwrap
  11.142+      (when-let ((slots (when slots
  11.143+			  (list-class-slots class slots exclude))))
  11.144+	(let ((slot-vals (list-slot-values-using-class class obj (remove-if #'null slots) nullp unboundp)))
  11.145+	  (if methods
  11.146+	      (push slot-vals res)
  11.147+	      (return-from unwrap (push slot-vals res)))))
  11.148+      (when-let ((methods (when methods (list-class-methods class methods indirect))))
  11.149+	(push methods res)))
  11.150+    (flatten res)))
  11.151+
  11.152+(defun wrap-object (class form)
  11.153+  "Given a CLASS prototype and an input FORM, return a new instance of
  11.154+CLASS. FORM is assumed to be the finalized lisp object which has
  11.155+already passed through `read' -- not a string or file-stream for
  11.156+example."
  11.157+  (declare (type class class)
  11.158+	   (type form form)))
  11.159+
  11.160+;; (defmacro define-fmt ())
  11.161+;; (defmacro define-macro ())
    12.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2+++ b/lisp/lib/sxp/tests.lisp	Sun Oct 15 03:20:41 2023 -0400
    12.3@@ -0,0 +1,38 @@
    12.4+;;; tests.lisp --- SEXP tests
    12.5+(pkg:defpkg :sxp.tests
    12.6+    (:use :cl :sxp :macs :rt)
    12.7+  (:export :*test-file* :*test-string*))
    12.8+
    12.9+(in-package :sxp.tests)
   12.10+(in-readtable *macs-readtable*)
   12.11+(declaim
   12.12+ (type (or string pathname) *test-file*)
   12.13+ (type string *test-string*))
   12.14+(defparameter *test-file* "tests.sxp")
   12.15+(defparameter *test-string* "(FOO 'BAR `(\"test\" ,BAZ ,@QUX) 123 0.0123 1/3 `(,A1 ,A2))")
   12.16+
   12.17+(defsuite :sxp)
   12.18+(in-suite :sxp)
   12.19+
   12.20+(deftest forms ()
   12.21+  (is (formp nil))
   12.22+  (is (formp t))
   12.23+  (is (formp 3.14))
   12.24+  (is (formp "string"))
   12.25+  (is (formp (mapc #`(',a1) '(a))))
   12.26+  (is (formp ())))
   12.27+
   12.28+(deftest sxp-file ()
   12.29+  (let ((f (read-sxp-file *test-file*)))
   12.30+    (is (equal (unwrap f) (unwrap f)))))
   12.31+
   12.32+(deftest sxp-string ()
   12.33+  (let ((f (make-instance 'sxp)))
   12.34+    (is (formp (read-sxp-string f *test-string*)))
   12.35+    (is (equalp (read-from-string (write-sxp-string f)) (read-from-string *test-string*)))))
   12.36+
   12.37+(deftest sxp-stream ()
   12.38+  (let ((f (make-instance 'sxp)))
   12.39+    (with-input-from-string (s *test-string*)
   12.40+      (read-sxp-stream f s))
   12.41+    (is (write-sxp-stream f nil))))
    13.1--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2+++ b/lisp/lib/sxp/tests.sxp	Sun Oct 15 03:20:41 2023 -0400
    13.3@@ -0,0 +1,42 @@
    13.4+; skip me maybe
    13.5+;; this file does not contain quote characters.
    13.6+(edges-1
    13.7+(
    13.8+(1389.886593 1341.567282)
    13.9+(1383.122623 1339.369530)
   13.10+)
   13.11+(
   13.12+(1383.122623 1339.369530)
   13.13+(1387.706464 1325.261939)
   13.14+)
   13.15+(
   13.16+(1387.706464 1325.261939)
   13.17+(1394.470360 1327.459664)
   13.18+)
   13.19+(
   13.20+(1394.470360 1327.459664)
   13.21+(1389.886593 1341.567282)
   13.22+)
   13.23+) ; edges end
   13.24+
   13.25+(edges-2
   13.26+( ( 1.1 2.2 ) (2.2 3.3) )
   13.27+( ( 2.2 3.3 ) (3.3 3.3) )
   13.28+( ( 3.3 3.3 ) (1.1 2.2) )
   13.29+) ; end edges of triangle room
   13.30+
   13.31+(= 4 4)
   13.32+(= 5 4)
   13.33+(> 4.0 54.0)
   13.34+(= 4 s)
   13.35+(= (= 4 4) (> 5 4))
   13.36+(not (= 3 3))
   13.37+(not 4)
   13.38+(if (= 4 4) 42 666)
   13.39+(if (= 4.0 4.0) (42))
   13.40+(+ 4 4)
   13.41+(+ 5.0 6.5)
   13.42+(- 4 5)
   13.43+(^ 2 3)
   13.44+(^ 3 2)
   13.45+(^ 3 (+ 2 1))
    14.1--- a/lisp/rdb.asd	Sun Oct 15 01:50:27 2023 -0400
    14.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    14.3@@ -1,20 +0,0 @@
    14.4-;;; rdb.asd --- thin RocksDB ORM
    14.5-(defsystem "rdb"
    14.6-  :version "0.1.0"
    14.7-  :license (:file "LICENSE")
    14.8-  :maintainer "ellis <ellis@rwest.io>"
    14.9-  :homepage "https://nas-t.net"
   14.10-  :bug-tracker "https://lab.rwest.io/comp/core/issues"
   14.11-  :depends-on (:macs :rocksdb)
   14.12-  :in-order-to ((test-op (test-op "rdb/tests")))
   14.13-  :components ((:file "rdb/rdb")))
   14.14-
   14.15-(defsystem "rdb/tests"
   14.16-  :version "0.1.0"
   14.17-  :license (:file "LICENSE")
   14.18-  :maintainer "ellis <ellis@rwest.io>"
   14.19-  :homepage "https://nas-t.net"
   14.20-  :bug-tracker "https://lab.rwest.io/comp/core/issues"
   14.21-  :depends-on (:rdb :rt)
   14.22-  :components ((:file "rdb/tests"))
   14.23-  :perform (test-op (op c) (uiop:symbol-call '#:rt '#:do-tests :rdb)))
    15.1--- a/lisp/rdb/rdb.lisp	Sun Oct 15 01:50:27 2023 -0400
    15.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.3@@ -1,204 +0,0 @@
    15.4-;;; rdb.lisp --- High-level RocksDB API
    15.5-
    15.6-;; a thin ORM for working with RocksDB storage. 
    15.7-
    15.8-;; Low-level bindings are in rocksdb.lisp.
    15.9-
   15.10-;; Commentary:
   15.11-
   15.12-;; Code:
   15.13-(pkg:defpkg :rdb
   15.14-  (:use :cl :alien :fu :rocksdb)
   15.15-  (:import-from :sb-ext :string-to-octets :octets-to-string)
   15.16-  (:reexport :rocksdb)
   15.17-  (:export 
   15.18-   ;; opts
   15.19-   :make-rdb-opts
   15.20-   :rdb-opts
   15.21-   :default-rdb-opts
   15.22-   ;; db
   15.23-   :open-db
   15.24-   :with-open-db 
   15.25-   ;; iter
   15.26-   :create-iter :with-iter
   15.27-   :iter-key :iter-key-str
   15.28-   :iter-val :iter-val-str
   15.29-   ;; err
   15.30-   :unable-to-open-db 
   15.31-   :unable-to-put-key-value-to-db 
   15.32-   :unable-to-get-value-to-db))
   15.33-
   15.34-(in-package :rdb)
   15.35-
   15.36-(defstruct rdb-opts
   15.37-  (create-if-missing nil :type boolean)
   15.38-  (total-threads 1 :type integer) ;; numcpus is default
   15.39-  (max-open-files 10000 :type integer)
   15.40-  (use-fsync nil :type boolean)
   15.41-  (disable-auto-compations nil :type boolean))
   15.42-
   15.43-;; unsafe
   15.44-(defun bind-rocksdb-opts% (opts)
   15.45-  (let ((o (rocksdb-options-create)))
   15.46-    (with-slots (create-if-missing total-threads) opts
   15.47-      (rocksdb-options-set-create-if-missing o create-if-missing)
   15.48-      (rocksdb-options-increase-parallelism o total-threads))
   15.49-    o))
   15.50-
   15.51-(defun default-rdb-opts () 
   15.52-  (make-rdb-opts
   15.53-   :create-if-missing t 
   15.54-   :total-threads 4))
   15.55-
   15.56-(defun default-rocksdb-options% ()
   15.57-  (bind-rocksdb-opts% (default-rdb-opts)))
   15.58-
   15.59-(defmacro with-open-db ((db-var db-path &optional opt) &body body)
   15.60-  `(let ((,db-var (open-db ,db-path ,opt)))
   15.61-     (unwind-protect (progn ,@body)
   15.62-       (rocksdb-close ,db-var))))
   15.63-
   15.64-(defmacro with-iter ((iter-var db &optional opt) &body body)
   15.65-  `(let ((,iter-var (create-iter ,db ,opt)))
   15.66-     (unwind-protect (progn ,@body)
   15.67-       (rocksdb-iter-destroy ,iter-var))))
   15.68-
   15.69-;;; Conditions
   15.70-(define-condition unable-to-open-db (error)
   15.71-  ((db-path :initarg :db-path
   15.72-            :reader db-path)
   15.73-   (error-message :initarg :error-message
   15.74-                  :reader error-message)))
   15.75-
   15.76-(defmethod print-object ((obj unable-to-open-db) stream)
   15.77-  (print-unreadable-object (obj stream :type t :identity t)
   15.78-    (format stream "error-message=~A" (error-message obj))))
   15.79-
   15.80-(define-condition unable-to-put-key-value-to-db (error)
   15.81-  ((db :initarg :db
   15.82-       :reader db)
   15.83-   (key :initarg :key
   15.84-        :reader key)
   15.85-   (val :initarg :val
   15.86-        :reader val)
   15.87-   (error-message :initarg :error-message
   15.88-                  :reader error-message)))
   15.89-
   15.90-(define-condition unable-to-get-value-to-db (error)
   15.91-  ((db :initarg :db
   15.92-       :reader db)
   15.93-   (key :initarg :key
   15.94-        :reader key)
   15.95-   (error-message :initarg :error-message
   15.96-                  :reader error-message)))
   15.97-
   15.98-;;; API
   15.99-(defun open-db (db-path &optional opts)
  15.100-  (let ((opts (if opts (bind-rocksdb-opts% opts) (default-rocksdb-options%))))
  15.101-    (with-alien ((e rocksdb-errptr))
  15.102-      (let* ((db-path (if (pathnamep db-path)
  15.103-                          (namestring db-path)
  15.104-                          db-path))
  15.105-             (db (rocksdb-open opts db-path e)))
  15.106-	(if (null-alien e)
  15.107-            db
  15.108-            (error 'unable-to-open-db
  15.109-                   :db-path db-path
  15.110-                   :error-message e))))))
  15.111-
  15.112-(defun put-kv (db key val &optional opts)
  15.113-  (let ((opts (or opts (rocksdb-writeoptions-create)))
  15.114-	(klen (length key))
  15.115-	(vlen (length val)))
  15.116-    (with-alien ((errptr rocksdb-errptr nil)
  15.117-		 (k (* char) (make-alien char klen))
  15.118-		 (v (* char) (make-alien char vlen)))
  15.119-      (loop for x across key
  15.120-	    for i from 0 below klen
  15.121-	    do (setf (deref k i) x))
  15.122-      (loop for y across val
  15.123-	    for i from 0 below vlen
  15.124-	    do (setf (deref v i) y))
  15.125-      (rocksdb-put db
  15.126-		   opts
  15.127-		   k
  15.128-		   klen
  15.129-		   v
  15.130-		   vlen
  15.131-		   errptr)
  15.132-      (unless (null-alien errptr)
  15.133-        (error 'unable-to-put-key-value-to-db
  15.134-                :db db
  15.135-                :key key
  15.136-                :val val
  15.137-                :error-message (alien-sap errptr))))))
  15.138-
  15.139-(defun put-kv-str (db key val &optional opt)
  15.140-  (let ((key-octets (string-to-octets key))
  15.141-        (val-octets (string-to-octets val)))
  15.142-    (put-kv db key-octets val-octets opt)))
  15.143-
  15.144-(defun get-kv (db key &optional opt)
  15.145-  (let ((opt (or opt (rocksdb-readoptions-create)))
  15.146-	(key (string-to-octets key))
  15.147-	(klen (length key)))
  15.148-    (with-alien ((vlen (* size-t))
  15.149-		 (errptr rocksdb-errptr nil)
  15.150-		 (k (* char) (make-alien char klen)))
  15.151-      (loop for x across key
  15.152-	    for i from 0 below klen
  15.153-	    do (setf (deref k i) x))
  15.154-
  15.155-      (let* ((val (rocksdb-get db
  15.156-			      opt
  15.157-			      k
  15.158-			      klen
  15.159-			      vlen
  15.160-			      errptr))
  15.161-	     (vlen (deref vlen)))
  15.162-	(unless (null-alien errptr)
  15.163-          (error 'unable-to-get-value-to-db
  15.164-		 :db db
  15.165-		 :key key
  15.166-		 :error-message (alien-sap errptr)))
  15.167-	;; helps if we know the vlen beforehand, would need a custom
  15.168-	;; C-side function probably.
  15.169-	(let ((v (make-array vlen :element-type 'unsigned-byte)))
  15.170-	  (loop for i from 0 below vlen
  15.171-		with x = (deref val i) 
  15.172-		do (setf (aref v i) x))
  15.173-	  (map 'vector #'code-char v))))))
  15.174-
  15.175- (defun get-kv-str (db key &optional opt)
  15.176-   (let ((k (string-to-octets key)))
  15.177-     (let ((v (get-kv db k opt)))
  15.178-       (when v (print v)))))
  15.179-
  15.180-(defun create-iter (db &optional opt)
  15.181-  (unless opt
  15.182-    (setq opt (rocksdb-readoptions-create)))
  15.183-  (rocksdb-create-iterator db opt))
  15.184-
  15.185-(defun iter-key (iter)
  15.186-  (with-alien ((klen-ptr (* unsigned-int)))
  15.187-    (let* ((key-ptr (rocksdb-iter-key iter klen-ptr))
  15.188-           (klen (deref klen-ptr))
  15.189-           (k (make-array klen :element-type '(unsigned-byte 8))))
  15.190-      (loop for i from 0 below klen with x = (deref key-ptr i) do (setf (aref k i) x))
  15.191-      k)))
  15.192-
  15.193-(defun iter-key-str (iter)
  15.194-  (when-let ((k (iter-key iter)))
  15.195-    (octets-to-string k)))
  15.196-
  15.197- (defun iter-val (iter)
  15.198-   (with-alien ((vlen-ptr (* unsigned-int)))
  15.199-     (let* ((val-ptr (rocksdb-iter-value iter vlen-ptr))
  15.200-            (vlen (deref vlen-ptr))
  15.201-            (v (make-array vlen :element-type '(unsigned-byte 8))))
  15.202-       (loop for i from 0 below vlen with x = (deref val-ptr i) do (setf (aref v i) x))
  15.203-       v)))
  15.204-
  15.205- (defun iter-val-str (iter)
  15.206-   (when-let ((v (iter-val iter)))
  15.207-     (octets-to-string v)))
    16.1--- a/lisp/rdb/tests.lisp	Sun Oct 15 01:50:27 2023 -0400
    16.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.3@@ -1,5 +0,0 @@
    16.4-(defpackage :rdb.tests
    16.5-  (:use :cl :rt :rdb))
    16.6-(in-package :rdb.tests)
    16.7-(defsuite :rdb)
    16.8-(in-suite :rdb)
    17.1--- a/lisp/sxp.asd	Sun Oct 15 01:50:27 2023 -0400
    17.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    17.3@@ -1,27 +0,0 @@
    17.4-;;; sxp.asd
    17.5-(defsystem "sxp"
    17.6-  :version "0.1.0"
    17.7-  :author "ellis <ellis@rwest.io>"
    17.8-  :maintainer "ellis <ellis@rwest.io>"
    17.9-  :depends-on ("macs")
   17.10-  :description "S-eXPressions"
   17.11-  :homepage "https://rwest.io/sxp"
   17.12-  :bug-tracker "https://lab.rwest.io/comp/sxp/issues"
   17.13-  :source-control "https://lab.rwest.io/comp/sxp"
   17.14-  :license "WTF"
   17.15-  :in-order-to ((test-op (test-op :sxp/tests)))
   17.16-  :components ((:file "sxp/sxp")))
   17.17-
   17.18-(defmethod perform :after ((op load-op) (c (eql (find-system :sxp))))
   17.19-  (pushnew :sxp *features*))
   17.20-
   17.21-(defsystem "sxp/tests"
   17.22-  :depends-on ("sxp" "rt" "uiop")
   17.23-  :components ((:file "sxp/tests"))
   17.24-  :perform (test-op (op c)
   17.25-		    (uiop:symbol-call '#:rt '#:do-tests)))
   17.26-
   17.27-(defsystem "sxp/bench"
   17.28-  :depends-on ("sxp" "uiop" "sb-sprof" "flamegraph")
   17.29-  :components ((:file "sxp/bench"))
   17.30-  :perform (test-op (op c) (uiop:symbol-call '#:sxp-bench '#:run-bench)))
    18.1--- a/lisp/sxp/bench.lisp	Sun Oct 15 01:50:27 2023 -0400
    18.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    18.3@@ -1,49 +0,0 @@
    18.4-(require :sb-sprof)
    18.5-(defpackage :sxp-bench
    18.6-  (:use :cl :sxp :sb-ext :sb-unix)
    18.7-  (:export :run-bench :*bench-input-file* :*bench-input-string* :*bench-input-object*
    18.8-	   :*bench-output-directory* :*bench-iterations* :*bench-report-file* ;:*bench-flamegraph-file*
    18.9-	   ))
   18.10-(in-package :sxp-bench)
   18.11-(declaim
   18.12- (type (or string pathname) *bench-input-file* *bench-output-directory* *bench-report-file*)
   18.13- (type string *bench-input-string*)
   18.14- (type sxp *bench-input-object*)
   18.15- (type integer *bench-iterations*))
   18.16-(defparameter *bench-input-file* "tests.sxp")
   18.17-(defparameter *bench-input-string* (uiop:read-file-string *bench-input-file*))
   18.18-(defparameter *bench-input-object* (make-instance 'sxp))
   18.19-(read-sxp-string *bench-input-object* *bench-input-string*)
   18.20-
   18.21-(defparameter *bench-output-directory* "/tmp/sxp-bench")
   18.22-(defparameter *bench-iterations* 1000)
   18.23-(defparameter *bench-report-file* "bench.sxp")
   18.24-;; (defparameter *bench-flamegraph-file* "bench.stack")
   18.25-(defmacro bench (&body body)
   18.26-  `(loop for i from 1 to *bench-iterations*
   18.27-	 do ,@body))
   18.28-
   18.29-(defun rbench (fn input)
   18.30-  (let ((res))
   18.31-    (bench (call-with-timing (lambda (&rest x) (push (cons i x) res)) fn input))
   18.32-    (nreverse res)))
   18.33-
   18.34-(defun wbench (fn)
   18.35-  (let ((res))
   18.36-    (bench (let ((out (make-pathname :name (format nil "~d.sxp" i) :directory *bench-output-directory*)))
   18.37-	     (call-with-timing (lambda (&rest x) (push (cons i x) res)) fn *bench-input-object* out :if-exists :supersede)))
   18.38-    (nreverse res)))
   18.39-
   18.40-(defun run-bench (&optional rpt)
   18.41-  (when (probe-file *bench-output-directory*)
   18.42-    (sb-ext:delete-directory *bench-output-directory* :recursive t))
   18.43-  (sb-unix:unix-mkdir *bench-output-directory* #o777)
   18.44-  (let ((rres (sb-sprof:with-profiling (:sample-interval 0.001) (rbench #'sxp:read-sxp-file *bench-input-file*)))
   18.45-	(wres (sb-sprof:with-profiling (:sample-interval 0.001) (wbench #'sxp:write-sxp-file))))
   18.46-    (if rpt
   18.47-	(progn
   18.48-	  (format t "Writing output to ~s" *bench-report-file*)
   18.49-	  (uiop:with-output-file (out *bench-report-file* :if-exists :supersede :if-does-not-exist :create)
   18.50-	    (print `(,@rres ,@wres) out)))
   18.51-	(print (list rres wres))))
   18.52-  (terpri))
    19.1--- a/lisp/sxp/sxp.lisp	Sun Oct 15 01:50:27 2023 -0400
    19.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    19.3@@ -1,158 +0,0 @@
    19.4-;;; sxp.lisp --- S-eXPressions
    19.5-
    19.6-;; sxp is a unified S-Expression data format
    19.7-
    19.8-;;; Code:
    19.9-(defpackage :sxp
   19.10-  (:use :cl :sb-mop :sym :fu :reexport)
   19.11-  (:import-from :uiop :read-file-forms :slurp-stream-forms :with-output-file)
   19.12-  ;; TODO: hot-patch readtables into sxp classes/parsers
   19.13-  (:import-from :readtables :defreadtable :in-readtable)
   19.14-  (:export
   19.15-   :sxp-fmt-designator
   19.16-   :form :formp :sxp-error :sxp-fmt-error :sxp-syntax-error :reader :writer :fmt
   19.17-   :wrap :wrap! :wrap-from-string! :unwrap :unwrap! :unwrap-or
   19.18-   :sxpp :build-ast :load-ast :ast
   19.19-   :define-macro :define-fmt :read-sxp-file :write-sxp-file
   19.20-   :read-sxp-string :write-sxp-string :read-sxp-stream :write-sxp-stream
   19.21-   :make-sxp :sxp :formp :form
   19.22-   :wrap-object :unwrap-object))
   19.23-
   19.24-(in-package :sxp)
   19.25-
   19.26-(defun formp (form)
   19.27-  (or (consp form) (atom form)))
   19.28-
   19.29-(deftype form ()
   19.30-  '(satisfies formp))
   19.31-
   19.32-;;; Conditions
   19.33-(define-condition sxp-error (error) ())
   19.34-
   19.35-(define-condition sxp-fmt-error (sxp-error)
   19.36-  ((format-control :initarg :format-control :reader format-control)
   19.37-   (format-arguments :initarg :format-arguments :reader format-arguments))
   19.38-  (:report (lambda (c s)
   19.39-             (apply 'format s (format-control c) (format-arguments c)))))
   19.40-
   19.41-(define-condition sxp-syntax-error (sxp-error) ())
   19.42-
   19.43- ;;; Protocol
   19.44-(defgeneric wrap (self form))
   19.45-(defgeneric wrap! (self form))
   19.46-(defgeneric wrap-from-string! (self str))
   19.47-(defgeneric unwrap (self))
   19.48-(defgeneric unwrap! (self))
   19.49-(defgeneric unwrap-or (self lambda))
   19.50-(defgeneric sxpp (self form))
   19.51-
   19.52-(defgeneric write-sxp-stream (self stream &key pretty case))
   19.53-(defgeneric read-sxp-stream (self stream))
   19.54-
   19.55-(defgeneric build-ast (self &key &allow-other-keys)
   19.56-  (:documentation "build the sxp representation of SELF and store it in the :ast
   19.57-slot. The :ast slot is always ignored."))
   19.58-
   19.59-(defgeneric load-ast (self)
   19.60-  (:documentation "load the object SELF from the :ast slot."))
   19.61-
   19.62-;;; Objects
   19.63-(defclass sxp ()
   19.64-  ((ast :initarg :ast :type form :accessor ast))
   19.65-  (:documentation "Dynamic class representing a SXP form."))
   19.66-
   19.67-(defmethod wrap! ((self sxp) form) (setf (slot-value self 'ast) (ignore-errors form)))
   19.68-
   19.69-(defmethod wrap-from-string! ((self sxp) str) (setf (slot-value self 'ast) (ignore-errors (read str))))
   19.70-
   19.71-(defmethod wrap ((self sxp) form) (setf (slot-value self 'ast) form))
   19.72-
   19.73-(defmethod unwrap ((self sxp)) (slot-value self 'ast))
   19.74-
   19.75-(defmethod unwrap! ((self sxp)) (ignore-errors (slot-value self 'ast)))
   19.76-
   19.77-(defmethod unwrap-or ((self sxp) else-fn)
   19.78-  (if (slot-unbound 'sxp self 'ast)
   19.79-      (slot-value self 'ast)
   19.80-      (if (null (slot-value self 'ast))
   19.81-	  (funcall else-fn))))
   19.82-
   19.83-(defmethod write-sxp-stream ((self sxp) stream &key (pretty *print-pretty*) (case :downcase))
   19.84-  (write (ast self)
   19.85-	 :stream stream
   19.86-	 :pretty pretty
   19.87-	 :case case))
   19.88-
   19.89-(defmethod read-sxp-stream ((self sxp) stream)
   19.90-  (setf (ast self) (slurp-stream-forms stream :count nil)))
   19.91-
   19.92-;; (defsetf unwrap ) (defsetf wrap )
   19.93-
   19.94-;;; Functions
   19.95-(defun read-sxp-file (file)
   19.96-  (make-instance 'sxp :ast (read-file-forms file)))
   19.97-
   19.98-(defun write-sxp-file (sxp file &optional &key if-exists)
   19.99-  (with-output-file (out file) :if-exists if-exists
  19.100-    (write-sxp-stream sxp out)))
  19.101-
  19.102-(defun read-sxp-string (self str) (with-input-from-string (s str) (read-sxp-stream self s)))
  19.103-
  19.104-(defun write-sxp-string (sxp) 
  19.105-  (let ((ast (ast sxp)))
  19.106-    (if (> (length ast) 1)
  19.107-	(write-to-string ast)
  19.108-	(write-to-string (car ast)))))
  19.109-
  19.110-(defun make-sxp (&rest form) (make-instance 'sxp :ast form))
  19.111-
  19.112-(deftype sxp-fmt-designator () '(member :canonical :collapsed)) 
  19.113-
  19.114-(defun unwrap-object (obj &key (slots t) (methods nil)
  19.115-			    (indirect nil) (tag nil)
  19.116-			    (unboundp nil) (nullp nil)
  19.117-			    (exclude nil))
  19.118-  "Build and return a new `form' from OBJ by traversing the class
  19.119-definition. This differs from the generic function `unwrap' which
  19.120-always uses the ast slot as an internal buffer. We can also call this
  19.121-on any class instance (doesn't need to subclass `sxp').
  19.122-
  19.123-SLOTS specifies the slots to be included in the output. If the value
  19.124-is t, all slots are included. The ast slot is not included by default,
  19.125-but this behavior may change in future revisions.
  19.126-
  19.127-When INDIRECT is non-nil, also include methods which indirectly
  19.128-specialize on OBJ.
  19.129-
  19.130-When TAG is non-nil, return a cons where car is TAG and cdr is the
  19.131-output. If TAG is t, use the class-name symbol."
  19.132-  (declare (type standard-object obj)
  19.133-	   (type (or list boolean) slots)
  19.134-	   (type (or list boolean) methods)
  19.135-	   (type boolean indirect)
  19.136-	   (type list exclude))
  19.137-  (unless (or slots methods)
  19.138-    (error "Required one missing key arg: SLOTS or METHODS"))
  19.139-  (let* ((class (class-of obj))
  19.140-	(res (when tag (list (if (eq t tag) (class-name class) tag)))))
  19.141-    (block unwrap
  19.142-      (when-let ((slots (when slots
  19.143-			  (list-class-slots class slots exclude))))
  19.144-	(let ((slot-vals (list-slot-values-using-class class obj (remove-if #'null slots) nullp unboundp)))
  19.145-	  (if methods
  19.146-	      (push slot-vals res)
  19.147-	      (return-from unwrap (push slot-vals res)))))
  19.148-      (when-let ((methods (when methods (list-class-methods class methods indirect))))
  19.149-	(push methods res)))
  19.150-    (flatten res)))
  19.151-
  19.152-(defun wrap-object (class form)
  19.153-  "Given a CLASS prototype and an input FORM, return a new instance of
  19.154-CLASS. FORM is assumed to be the finalized lisp object which has
  19.155-already passed through `read' -- not a string or file-stream for
  19.156-example."
  19.157-  (declare (type class class)
  19.158-	   (type form form)))
  19.159-
  19.160-;; (defmacro define-fmt ())
  19.161-;; (defmacro define-macro ())
    20.1--- a/lisp/sxp/tests.lisp	Sun Oct 15 01:50:27 2023 -0400
    20.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.3@@ -1,38 +0,0 @@
    20.4-;;; tests.lisp --- SEXP tests
    20.5-;; TODO 2023-09-01: refactor to macs.rt
    20.6-(defpackage :sxp-tests
    20.7-  (:use :cl :sxp :macs :rt)
    20.8-  (:export #:run-tests))
    20.9-(in-package :sxp-tests)
   20.10-(in-readtable *macs-readtable*)
   20.11-(declaim
   20.12- (type (or string pathname) *test-file*)
   20.13- (type string *test-string*))
   20.14-(defparameter *test-file* "tests.sxp")
   20.15-(defparameter *test-string* "(FOO 'BAR `(\"test\" ,BAZ ,@QUX) 123 0.0123 1/3 `(,A1 ,A2))")
   20.16-
   20.17-(defsuite :sxp)
   20.18-(in-suite :sxp)
   20.19-
   20.20-(deftest forms ()
   20.21-  (is (formp nil))
   20.22-  (is (formp t))
   20.23-  (is (formp 3.14))
   20.24-  (is (formp "string"))
   20.25-  (is (formp (mapc #`(',a1) '(a))))
   20.26-  (is (formp ())))
   20.27-
   20.28-(deftest sxp-file ()
   20.29-  (let ((f (read-sxp-file *test-file*)))
   20.30-    (is (equal (unwrap f) (unwrap f)))))
   20.31-
   20.32-(deftest sxp-string ()
   20.33-  (let ((f (make-instance 'sxp)))
   20.34-    (is (formp (read-sxp-string f *test-string*)))
   20.35-    (is (equalp (read-from-string (write-sxp-string f)) (read-from-string *test-string*)))))
   20.36-
   20.37-(deftest sxp-stream ()
   20.38-  (let ((f (make-instance 'sxp)))
   20.39-    (with-input-from-string (s *test-string*)
   20.40-      (read-sxp-stream f s))
   20.41-    (is (write-sxp-stream f nil))))
    21.1--- a/lisp/sxp/tests.sxp	Sun Oct 15 01:50:27 2023 -0400
    21.2+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
    21.3@@ -1,42 +0,0 @@
    21.4-; skip me maybe
    21.5-;; this file does not contain quote characters.
    21.6-(edges-1
    21.7-(
    21.8-(1389.886593 1341.567282)
    21.9-(1383.122623 1339.369530)
   21.10-)
   21.11-(
   21.12-(1383.122623 1339.369530)
   21.13-(1387.706464 1325.261939)
   21.14-)
   21.15-(
   21.16-(1387.706464 1325.261939)
   21.17-(1394.470360 1327.459664)
   21.18-)
   21.19-(
   21.20-(1394.470360 1327.459664)
   21.21-(1389.886593 1341.567282)
   21.22-)
   21.23-) ; edges end
   21.24-
   21.25-(edges-2
   21.26-( ( 1.1 2.2 ) (2.2 3.3) )
   21.27-( ( 2.2 3.3 ) (3.3 3.3) )
   21.28-( ( 3.3 3.3 ) (1.1 2.2) )
   21.29-) ; end edges of triangle room
   21.30-
   21.31-(= 4 4)
   21.32-(= 5 4)
   21.33-(> 4.0 54.0)
   21.34-(= 4 s)
   21.35-(= (= 4 4) (> 5 4))
   21.36-(not (= 3 3))
   21.37-(not 4)
   21.38-(if (= 4 4) 42 666)
   21.39-(if (= 4.0 4.0) (42))
   21.40-(+ 4 4)
   21.41-(+ 5.0 6.5)
   21.42-(- 4 5)
   21.43-(^ 2 3)
   21.44-(^ 3 2)
   21.45-(^ 3 (+ 2 1))
    22.1--- a/readme.org	Sun Oct 15 01:50:27 2023 -0400
    22.2+++ b/readme.org	Sun Oct 15 03:20:41 2023 -0400
    22.3@@ -1,29 +1,363 @@
    22.4 #+TITLE: comp/core
    22.5 * rust
    22.6-** btrfs
    22.7-** btrfsutil
    22.8+** ffi
    22.9+*** btrfs
   22.10+*** btrfsutil
   22.11 * lisp
   22.12 #+begin_src lisp :results silent
   22.13-  (let ((asds '("lisp/rdb.asd" "lisp/sxp.asd" 
   22.14-		"lisp/organ/organ.asd" "lisp/macs/macs.asd" "lisp/skel/skel.asd"
   22.15+  (let ((asds '("lisp/macs/macs.asd" 
   22.16+		"lisp/lib/rdb.asd" "lisp/lib/sxp.asd" "lisp/lib/organ/organ.asd" "lisp/lib/skel/skel.asd"
   22.17 		"lisp/ffi/rocksdb.asd" "lisp/ffi/btrfs.asd")))
   22.18     (mapc (lambda (x) (asdf:load-asd x)) asds))
   22.19 #+end_src
   22.20-** rdb
   22.21+** macs
   22.22 *** tests
   22.23 #+begin_src lisp :package rdb.tests :results output replace :exports results
   22.24+  (asdf:load-system :cli)
   22.25+  (asdf:load-system :macs/tests)
   22.26+  (in-package :macs.tests)
   22.27+  (load "lisp/macs/tests.lisp")
   22.28+  (setq *log-level* :debug)
   22.29+  (rt:do-tests :macs)
   22.30+#+end_src
   22.31+#+RESULTS:
   22.32+#+begin_example
   22.33+in suite MACS with 11/11 tests:
   22.34+:DEBUG @ 10521.106  
   22.35+; running test: 
   22.36+; #<TEST PAN :fn PAN-test1353 :args NIL :persist NIL {100445A003}>
   22.37+:DEBUG @ 10521.113  
   22.38+; #<PASS (= 0 (FUNCALL P NIL))>
   22.39+:DEBUG @ 10521.113  
   22.40+; #<PASS (= 1 (FUNCALL P 1))>
   22.41+:DEBUG @ 10521.113  
   22.42+; #<PASS (= 1 B C)>
   22.43+#<PASS PAN-TEST1353> 
   22.44+:DEBUG @ 10521.113  
   22.45+; running test: 
   22.46+; #<TEST ANA :fn ANA-test1352 :args NIL :persist NIL {10043B24C3}>
   22.47+:DEBUG @ 10521.117  
   22.48+; #<PASS (= 8 (AIF (+ 2 2) (+ IT IT)))>
   22.49+#<PASS ANA-TEST1352> 
   22.50+:DEBUG @ 10521.117  
   22.51+; running test: 
   22.52+; #<TEST FMT :fn FMT-test1351 :args NIL :persist NIL {1003D5F413}>
   22.53+:DEBUG @ 10521.12  
   22.54+; #<PASS (STRING= (FORMAT NIL | 1 | 2 | 3 |~%) (FMT-ROW '(1 2 3)))>
   22.55+:DEBUG @ 10521.12  
   22.56+; #<PASS (STRING= (FMT-SXHASH (SXHASH T)) (FMT-SXHASH (SXHASH T)))>
   22.57+:DEBUG @ 10521.12  
   22.58+; #<PASS (STRING= FOOBAR
   22.59+ ├─ :A
   22.60+ ├─ :B
   22.61+ ├─  C
   22.62+ ╰─  D
   22.63+
   22.64+                  FOOBAR
   22.65+ ├─ :A
   22.66+ ├─ :B
   22.67+ ├─  C
   22.68+ ╰─  D
   22.69+)>
   22.70+:DEBUG @ 10521.12  
   22.71+; #<PASS (STRING= SK-PROJECT
   22.72+ ├─ :NAME
   22.73+ │   ╰─ "foobar"
   22.74+ ├─ :PATH
   22.75+ │   ╰─ "/a/b/c.asd"
   22.76+ ╰─ :VC
   22.77+     ╰─ :HG
   22.78+
   22.79+                  SK-PROJECT
   22.80+ ├─ :NAME
   22.81+ │   ╰─ "foobar"
   22.82+ ├─ :PATH
   22.83+ │   ╰─ "/a/b/c.asd"
   22.84+ ╰─ :VC
   22.85+     ╰─ :HG
   22.86+)>
   22.87+#<PASS FMT-TEST1351> 
   22.88+:DEBUG @ 10521.12  
   22.89+; running test: 
   22.90+; #<TEST ALIEN :fn ALIEN-test1350 :args NIL :persist NIL {1003D5D903}>
   22.91+:DEBUG @ 10521.123  
   22.92+; #<PASS (= 0 (FOREIGN-INT-TO-INTEGER 0 4))>
   22.93+:DEBUG @ 10521.123  
   22.94+; #<PASS (= 1 (BOOL-TO-FOREIGN-INT T))>
   22.95+#<PASS ALIEN-TEST1350> 
   22.96+:DEBUG @ 10521.123  
   22.97+; running test: 
   22.98+; #<TEST THREAD :fn THREAD-test1349 :args NIL :persist NIL {1003D5BB43}>
   22.99+:DEBUG @ 10521.127  
  22.100+; #<PASS (STRINGP (PRINT-THREAD-INFO NIL))>
  22.101+#<PASS THREAD-TEST1349> 
  22.102+:DEBUG @ 10521.127  
  22.103+; running test: 
  22.104+; #<TEST REEXPORT :fn REEXPORT-test1348 :args NIL :persist NIL {1003D5A633}>
  22.105+#<PASS REEXPORT-TEST1348> 
  22.106+:DEBUG @ 10521.127  
  22.107+; running test: 
  22.108+; #<TEST COND :fn COND-test1347 :args NIL :persist NIL {1003D59383}>
  22.109+#<PASS COND-TEST1347> 
  22.110+:DEBUG @ 10521.127  
  22.111+; running test: 
  22.112+; #<TEST LOG :fn LOG-test1346 :args NIL :persist NIL {1003D580A3}>
  22.113+:DEBUG @ 10521.127  
  22.114+; test
  22.115+; DEBUG
  22.116+:DEBUG @ 10521.127  
  22.117+; test
  22.118+; DEBUG
  22.119+:DEBUG @ 10521.13  
  22.120+; test
  22.121+; DEBUG
  22.122+:DEBUG @ 10521.13  
  22.123+; #<PASS (DEBUG! test *LOG-LEVEL*)>
  22.124+#<PASS LOG-TEST1346> 
  22.125+:DEBUG @ 10521.13  
  22.126+; running test: 
  22.127+; #<TEST LIST :fn LIST-test1345 :args NIL :persist NIL {1003CD8E43}>
  22.128+:DEBUG @ 10521.134  
  22.129+; #<PASS (EQ (ENSURE-CAR '(0)) (ENSURE-CAR 0))>
  22.130+:DEBUG @ 10521.134  
  22.131+; #<PASS (EQ (ENSURE-CAR '(NIL)) (ENSURE-CAR NIL))>
  22.132+:DEBUG @ 10521.134  
  22.133+; #<PASS (NOT (EQ (ENSURE-CONS 0) (ENSURE-CONS 0)))>
  22.134+:DEBUG @ 10521.134  
  22.135+; #<PASS (EQUAL (ENSURE-CONS 0) (ENSURE-CONS 0))>
  22.136+#<PASS LIST-TEST1345> 
  22.137+:DEBUG @ 10521.134  
  22.138+; running test: 
  22.139+; #<TEST STR :fn STR-test1344 :args NIL :persist NIL {1003BADEE3}>
  22.140+:DEBUG @ 10521.14  
  22.141+; #<PASS (TYPEP test 'STRING-DESIGNATOR)>
  22.142+:DEBUG @ 10521.14  
  22.143+; #<PASS (TYPEP 'TEST 'STRING-DESIGNATOR)>
  22.144+:DEBUG @ 10521.14  
  22.145+; #<PASS (TYPEP C 'STRING-DESIGNATOR)>
  22.146+:DEBUG @ 10521.14  
  22.147+; #<PASS (NOT (TYPEP 0 'STRING-DESIGNATOR))>
  22.148+#<PASS STR-TEST1344> 
  22.149+:DEBUG @ 10521.14  
  22.150+; running test: 
  22.151+; #<TEST SYM :fn SYM-test1343 :args NIL :persist NIL {10037F7453}>
  22.152+:DEBUG @ 10521.144  
  22.153+; #<PASS (NOT (EQUALP (MAKE-GENSYM 'A) (MAKE-GENSYM 'A)))>
  22.154+:DEBUG @ 10521.144  
  22.155+; #<PASS (EQ (ENSURE-SYMBOL 'TESTS MACS.TESTS) 'TESTS)>
  22.156+:DEBUG @ 10521.144  
  22.157+; #<PASS (EQ 'FOO (FORMAT-SYMBOL MACS.TESTS ~A 'FOO))>
  22.158+:DEBUG @ 10521.144  
  22.159+; #<PASS (EQ (MAKE-KEYWORD 'FIZZ) FIZZ)>
  22.160+#<PASS SYM-TEST1343> 
  22.161+No tests failed.
  22.162+#+end_example
  22.163+** lib
  22.164+*** cli
  22.165+#+begin_src lisp :package rdb.tests :results output replace :exports results
  22.166+  (asdf:load-system :cli)
  22.167+  (asdf:load-system :cli/tests)
  22.168+  (in-package :cli.tests)
  22.169+  (load "lisp/lib/cli/tests.lisp")
  22.170+  (setq *log-level* :debug)
  22.171+  (rt:do-tests :cli)
  22.172+#+end_src
  22.173+*** rt
  22.174+#+begin_src lisp :package rt.tests :results output replace :exports results
  22.175+  (asdf:load-system :rt/tests)
  22.176+  (in-package :rt.tests)
  22.177+  (load "lisp/lib/cli/tests.lisp")
  22.178+  (setq *log-level* :debug)
  22.179+  (do-tests :rt)
  22.180+#+end_src
  22.181+*** rdb
  22.182+**** tests
  22.183+#+begin_src lisp :package rdb.tests :results output replace :exports results
  22.184   (asdf:load-system :rdb/tests)
  22.185   (in-package :rdb.tests)
  22.186-  (load "lisp/rdb/tests.lisp")
  22.187-  (setq log:*log-level* :debug)
  22.188-  (rt:do-tests :rdb)
  22.189+  (load "lisp/lib/rdb/tests.lisp")
  22.190+  (setq *log-level* :debug)
  22.191+  (do-tests :rdb)
  22.192 #+end_src
  22.193-
  22.194 #+RESULTS:
  22.195 : in suite RDB with 0/0 tests:
  22.196 : No tests failed.
  22.197+*** sxp
  22.198+**** tests
  22.199+#+begin_src lisp :package sxp.tests :results output replace :exports results
  22.200+  (asdf:load-system :sxp/tests)
  22.201+  (load "lisp/lib/sxp/tests.lisp")
  22.202+  (in-package :sxp.tests)
  22.203+  (let ((*default-pathname-defaults* #.#P"./lisp/lib/sxp/")
  22.204+	(log:*log-level* :debug))
  22.205+    (do-tests :sxp))
  22.206+#+end_src
  22.207+#+RESULTS:
  22.208+#+begin_example
  22.209+in suite SXP with 4/4 tests:
  22.210+:DEBUG @ 10905.557  
  22.211+; running test: 
  22.212+; #<TEST SXP-STREAM :fn SXP-STREAM-test1461 :args NIL :persist NIL {1002F41C33}>
  22.213+((foo 'bar `("test" ,baz ,@qux) 123 0.0123 1/3 `(,a1 ,a2))):DEBUG @ 10905.563  
  22.214+; #<PASS (WRITE-SXP-STREAM F NIL)>
  22.215+#<PASS SXP-STREAM-TEST1461> 
  22.216+:DEBUG @ 10905.563  
  22.217+; running test: 
  22.218+; #<TEST SXP-STRING :fn SXP-STRING-test1460 :args NIL :persist NIL {1002DEF703}>
  22.219+:DEBUG @ 10905.566  
  22.220+; #<PASS (FORMP (READ-SXP-STRING F *TEST-STRING*))>
  22.221+:DEBUG @ 10905.566  
  22.222+; #<PASS (EQUALP (READ-FROM-STRING (WRITE-SXP-STRING F))
  22.223+                 (READ-FROM-STRING *TEST-STRING*))>
  22.224+#<PASS SXP-STRING-TEST1460> 
  22.225+:DEBUG @ 10905.566  
  22.226+; running test: 
  22.227+; #<TEST SXP-FILE :fn SXP-FILE-test1459 :args NIL :persist NIL {1002DEDBF3}>
  22.228+:DEBUG @ 10905.57  
  22.229+; #<PASS (EQUAL (UNWRAP F) (UNWRAP F))>
  22.230+#<PASS SXP-FILE-TEST1459> 
  22.231+:DEBUG @ 10905.57  
  22.232+; running test: 
  22.233+; #<TEST FORMS :fn FORMS-test1458 :args NIL :persist NIL {1002DEC3C3}>
  22.234+:DEBUG @ 10905.577  
  22.235+; #<PASS (FORMP NIL)>
  22.236+:DEBUG @ 10905.577  
  22.237+; #<PASS (FORMP T)>
  22.238+:DEBUG @ 10905.577  
  22.239+; #<PASS (FORMP 3.14)>
  22.240+:DEBUG @ 10905.577  
  22.241+; #<PASS (FORMP string)>
  22.242+:DEBUG @ 10905.577  
  22.243+; #<PASS (FORMP (MAPC (LAMBDA (A1) `(',A1)) '(A)))>
  22.244+:DEBUG @ 10905.577  
  22.245+; #<PASS (FORMP NIL)>
  22.246+#<PASS FORMS-TEST1458> 
  22.247+No tests failed.
  22.248+#+end_example
  22.249+*** organ
  22.250+**** tests
  22.251+#+begin_src lisp :package organ.tests :results output replace :exports results
  22.252+  (asdf:load-system :organ/tests)
  22.253+  (in-package :organ.tests)
  22.254+  (setq log:*log-level* :debug)
  22.255+  (load "lisp/lib/organ/tests.lisp")
  22.256+  (rt:do-tests :organ)
  22.257+#+end_src
  22.258+#+RESULTS:
  22.259+#+begin_example
  22.260+in suite ORGAN with 3/3 tests:
  22.261+:DEBUG @ 12527.026  
  22.262+; running test: 
  22.263+; #<TEST ORG-HEADLINE :fn ORG-HEADLINE-test18308 :args NIL :persist NIL {1005FBD213}>
  22.264+:DEBUG @ 12527.037  
  22.265+; #<PASS (= (LEVEL (ORG-PARSE (MAKE-ORG-HEADLINE S))) 2)>
  22.266+:DEBUG @ 12527.04  
  22.267+; #<PASS (STRING= (TITLE (ORG-PARSE (MAKE-ORG-HEADLINE S))) DONE testing stuff )>
  22.268+:DEBUG @ 12527.04  
  22.269+; #<PASS (= (LENGTH (TAGS (ORG-PARSE (MAKE-ORG-HEADLINE S)))) 2)>
  22.270+#<PASS ORG-HEADLINE-TEST18308> 
  22.271+:DEBUG @ 12527.04  
  22.272+; running test: 
  22.273+; #<TEST ORG-LINES :fn ORG-LINES-test18307 :args NIL :persist NIL {1005FBAEE3}>
  22.274+:DEBUG @ 12527.043  
  22.275+; #<PASS (READ-ORG-LINES (OPEN *TEST-FILE*))>
  22.276+:DEBUG @ 12527.043  
  22.277+; #<PASS (READ-ORG-LINES-FROM-STRING S)>
  22.278+#<PASS ORG-LINES-TEST18307> 
  22.279+:DEBUG @ 12527.043  
  22.280+; running test: 
  22.281+; #<TEST ORG-FILE :fn ORG-FILE-test18306 :args NIL :persist NIL {1005FB96E3}>
  22.282+:DEBUG @ 12527.047  
  22.283+; #<PASS (READ-ORG-FILE *TEST-FILE*)>
  22.284+#<PASS ORG-FILE-TEST18306> 
  22.285+No tests failed.
  22.286+#+end_example
  22.287+*** skel
  22.288+**** tests
  22.289+#+begin_src lisp :package skel.tests :results output replace :exports results
  22.290+  (asdf:load-system :skel/tests)
  22.291+  (in-package :skel.tests)
  22.292+  (load "lisp/lib/skel/tests.lisp")
  22.293+  (setq *log-level* :debug)
  22.294+  (rt:do-tests :skel)
  22.295+#+end_src
  22.296+#+RESULTS:
  22.297+#+begin_example
  22.298+; compiling file "/home/ellis/dev/skel/tests.lisp" (written 15 OCT 2023 03:10:25 AM):
  22.299 
  22.300-** sxp
  22.301+; wrote /home/ellis/.cache/common-lisp/sbcl-2.3.8.18.master.74-8cf7faf9a-linux-x64/home/ellis/dev/skel/tests-tmpR8PK79V8.fasl
  22.302+; compilation finished in 0:00:00.006
  22.303+in suite SKEL with 6/6 tests:
  22.304+:DEBUG @ 12500.617  
  22.305+; running test: 
  22.306+; #<TEST VM :fn VM-test17295 :args NIL :persist NIL {100713BF53}>
  22.307+:DEBUG @ 12500.623  
  22.308+; #<PASS (LET ((VM (MAKE-SK-VM C9)))
  22.309+           (DOTIMES (I C8) (SKS-POP VM))
  22.310+           T)>
  22.311+:DEBUG @ 12500.623  
  22.312+; #<PASS (SKS-POP VM)>
  22.313+#<PASS VM-TEST17295> 
  22.314+:DEBUG @ 12500.623  
  22.315+; running test: 
  22.316+; #<TEST MAKEFILE :fn MAKEFILE-test17294 :args NIL :persist NIL {100713A443}>
  22.317+:DEBUG @ 12500.646  
  22.318+; #<PASS (NULL (SK-WRITE-FILE (MK) IF-EXISTS SUPERSEDE PATH (TMP-PATH mk)))>
  22.319+:DEBUG @ 12500.646  
  22.320+; #<PASS (PUSH-RULE R1 MK1)>
  22.321+:DEBUG @ 12500.646  
  22.322+; #<PASS (PUSH-RULE R2 MK1)>
  22.323+:DEBUG @ 12500.646  
  22.324+; #<PASS (PUSH-DIRECTIVE
  22.325+          (CMD ifeq ($(DEBUG),1) echo foo 
  22.326+endif)
  22.327+          MK1)>
  22.328+:DEBUG @ 12500.646  
  22.329+; #<PASS (PUSH-VAR '(A B) MK1)>
  22.330+:DEBUG @ 12500.646  
  22.331+; #<PASS (PUSH-VAR '(B C) MK1)>
  22.332+#<PASS MAKEFILE-TEST17294> 
  22.333+:DEBUG @ 12500.646  
  22.334+; running test: 
  22.335+; #<TEST SKELRC :fn SKELRC-test17293 :args NIL :persist NIL {1006FD6AF3}>
  22.336+#<PASS SKELRC-TEST17293> 
  22.337+:DEBUG @ 12500.65  
  22.338+; running test: 
  22.339+; #<TEST SKELFILE :fn SKELFILE-test17292 :args NIL :persist NIL {1006FD5843}>
  22.340+:DEBUG @ 12500.663  
  22.341+; #<PASS (SK-WRITE-FILE (MAKE-INSTANCE 'SK-PROJECT NAME nada PATH %TMP) PATH
  22.342+                        %TMP IF-EXISTS SUPERSEDE)>
  22.343+:DEBUG @ 12500.663  
  22.344+; #<FAIL (INIT-SKELFILE %TMP)>
  22.345+:DEBUG @ 12500.663  
  22.346+; #<PASS (LOAD-SKELFILE %TMP)>
  22.347+:DEBUG @ 12500.663  
  22.348+; #<PASS (BUILD-AST (SK-READ-FILE (MAKE-INSTANCE 'SK-PROJECT) %TMP))>
  22.349+#<PASS SKELFILE-TEST17292> 
  22.350+:DEBUG @ 12500.663  
  22.351+; running test: 
  22.352+; #<TEST HEADER-COMMENTS :fn HEADER-COMMENTS-test17291 :args NIL :persist NIL {1006FD3893}>
  22.353+:DEBUG @ 12500.667  
  22.354+; #<PASS (EQ
  22.355+          (TYPE-OF (MAKE-SHEBANG-FILE-HEADER (MAKE-SHEBANG-COMMENT /dev/null)))
  22.356+          'FILE-HEADER)>
  22.357+:DEBUG @ 12500.667  
  22.358+; #<PASS (EQ
  22.359+          (TYPE-OF
  22.360+           (MAKE-SOURCE-FILE-HEADER
  22.361+            (MAKE-SOURCE-HEADER-COMMENT foo-test TIMESTAMP T DESCRIPTION
  22.362+                                        nothing to see here OPTS
  22.363+                                        '(Definitely-Not_Emacs: T;))))
  22.364+          'FILE-HEADER)>
  22.365+#<PASS HEADER-COMMENTS-TEST17291> 
  22.366+:DEBUG @ 12500.667  
  22.367+; running test: 
  22.368+; #<TEST SANITY :fn SANITY-test17290 :args NIL :persist NIL {1006FD1D83}>
  22.369+:DEBUG @ 12500.67  
  22.370+; #<PASS (EQ T (APPLY #'/= (SKELS 3E8)))>
  22.371+#<PASS SANITY-TEST17290> 
  22.372+No tests failed.
  22.373+#+end_example
  22.374 ** ffi
  22.375 *** btrfs
  22.376 **** tests
  22.377@@ -34,11 +368,9 @@
  22.378   (setq log:*log-level* :debug)
  22.379   (rt:do-tests :btrfs)
  22.380 #+end_src
  22.381-
  22.382 #+RESULTS:
  22.383 : in suite BTRFS with 0/0 tests:
  22.384 : No tests failed.
  22.385-
  22.386 *** rocksdb
  22.387 **** tests
  22.388 #+begin_src lisp :package rocksdb.tests :results output replace :exports results
  22.389@@ -48,7 +380,6 @@
  22.390   (setq log:*log-level* :debug)
  22.391   (rt:do-tests :rocksdb)
  22.392 #+end_src
  22.393-
  22.394 #+RESULTS:
  22.395 #+begin_example
  22.396 in suite ROCKSDB with 2/2 tests: