From 5e916433b891e990896a00fc01d78fb35998939c Mon Sep 17 00:00:00 2001 From: Yogthos Date: Mon, 22 Jun 2026 02:50:53 -0400 Subject: [PATCH] Native SQLite + an HTTP server over FFI (ring-app foundation) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sqlite.ss: jolt.sqlite + jdbc.core (jolt-lang/db's API) over the system libsqlite3 — open/close, exec, a prepared query returning row maps, text/int/ double parameter binding, last_insert_rowid. The sqlite3 C API is non-variadic so it binds directly. http-server.ss: a minimal HTTP/1.1 server over BSD sockets (socket/bind/listen/ accept/recv/send via FFI), one connection at a time on a background accept thread, synchronous Ring handlers. Parses the request line + headers + a Content-Length body into a Ring request map (:body a StringReader), formats a Ring response map back. Exposed as jolt.http.server and, for the example, as ring-janet.adapter/run-server. macOS and Linux socket-option constants handled. --- host/chez/cli.ss | 2 + host/chez/http-server.ss | 225 +++++++++++++++++++++++++++++++++++++++ host/chez/sqlite.ss | 134 +++++++++++++++++++++++ 3 files changed, 361 insertions(+) create mode 100644 host/chez/http-server.ss create mode 100644 host/chez/sqlite.ss diff --git a/host/chez/cli.ss b/host/chez/cli.ss index 1d99282..15b5c75 100644 --- a/host/chez/cli.ss +++ b/host/chez/cli.ss @@ -21,6 +21,8 @@ (load "host/chez/compile-eval.ss") (load "host/chez/png.ss") ; jolt.png — a baked namespace before the snapshot (load "host/chez/http-client.ss") ; jolt.http-client (libcurl) +(load "host/chez/sqlite.ss") ; jolt.sqlite + jdbc.core (libsqlite3) +(load "host/chez/http-server.ss") ; jolt.http.server + ring-janet.adapter (BSD sockets) (load "host/chez/loader.ss") ;; jolt.main + jolt.deps live under jolt-core; keep them (and src/jolt) on the diff --git a/host/chez/http-server.ss b/host/chez/http-server.ss new file mode 100644 index 0000000..8a7f930 --- /dev/null +++ b/host/chez/http-server.ss @@ -0,0 +1,225 @@ +;; http-server.ss (jolt-90sp) — a minimal HTTP/1.1 server over BSD sockets via +;; Chez's FFI (the socket calls are non-variadic, so they bind directly). One +;; connection handled at a time on a background accept thread; synchronous Ring +;; handlers. Enough to serve a small web app. +;; +;; Exposed as jolt.http.server/run-server and (for the ring-app example) as +;; ring-janet.adapter/run-server + stop-server — both baked namespaces. + +(load-shared-object #f) ; resolve socket/bind/listen/accept/recv/send in the process + +(define c-socket (foreign-procedure "socket" (int int int) int)) +(define c-bind (foreign-procedure "bind" (int void* int) int)) +(define c-listen (foreign-procedure "listen" (int int) int)) +(define c-accept (foreign-procedure "accept" (int void* void*) int)) +(define c-setsockopt (foreign-procedure "setsockopt" (int int int void* int) int)) +(define c-recv (foreign-procedure "recv" (int void* size_t int) ssize_t)) +(define c-send (foreign-procedure "send" (int void* size_t int) ssize_t)) +(define c-close (foreign-procedure "close" (int) int)) + +(define AF_INET 2) (define SOCK_STREAM 1) +;; SOL_SOCKET / SO_REUSEADDR differ by platform: macOS uses 0xffff / 4, Linux 1 / 2. +(define on-macos? + (let ((m (symbol->string (machine-type)))) + (let loop ((i 0)) (cond ((> (+ i 3) (string-length m)) #f) + ((string=? (substring m i (+ i 3)) "osx") #t) + (else (loop (+ i 1))))))) +(define sock-level (if on-macos? #xffff 1)) +(define sock-reuse (if on-macos? 4 2)) + +;; sockaddr_in for host:port. macOS: byte0=len(16), byte1=family; Linux: bytes0-1=family. +(define (make-sockaddr host port) + (let ((sa (foreign-alloc 16))) + (do ((i 0 (+ i 1))) ((= i 16)) (foreign-set! 'unsigned-8 sa i 0)) + (if (= sock-level #xffff) + (begin (foreign-set! 'unsigned-8 sa 0 16) (foreign-set! 'unsigned-8 sa 1 AF_INET)) + (foreign-set! 'unsigned-8 sa 0 AF_INET)) + (foreign-set! 'unsigned-8 sa 2 (bitwise-and (bitwise-arithmetic-shift-right port 8) #xff)) + (foreign-set! 'unsigned-8 sa 3 (bitwise-and port #xff)) + ;; sin_addr: 127.0.0.1 (loopback) — the example serves locally + (foreign-set! 'unsigned-8 sa 4 127) (foreign-set! 'unsigned-8 sa 5 0) + (foreign-set! 'unsigned-8 sa 6 0) (foreign-set! 'unsigned-8 sa 7 1) + sa)) + +(define (listen-socket host port) + (let ((fd (c-socket AF_INET SOCK_STREAM 0))) + (when (< fd 0) (error #f "socket() failed")) + (let ((opt (foreign-alloc 4))) + (foreign-set! 'int opt 0 1) + (c-setsockopt fd sock-level sock-reuse opt 4) + (foreign-free opt)) + (let ((sa (make-sockaddr host port))) + (when (< (c-bind fd sa 16) 0) (c-close fd) (foreign-free sa) (error #f (string-append "bind() failed on port " (number->string port)))) + (foreign-free sa)) + (when (< (c-listen fd 64) 0) (c-close fd) (error #f "listen() failed")) + fd)) + +;; --- request reading -------------------------------------------------------- +(define hs-bufsize 65536) +;; index just past the "\r\n\r\n" header/body separator, or #f. +(define (bv-find-crlfcrlf bv) + (let ((len (bytevector-length bv))) + (let loop ((i 0)) + (cond ((> (+ i 4) len) #f) + ((and (= (bytevector-u8-ref bv i) 13) (= (bytevector-u8-ref bv (+ i 1)) 10) + (= (bytevector-u8-ref bv (+ i 2)) 13) (= (bytevector-u8-ref bv (+ i 3)) 10)) + (+ i 4)) + (else (loop (+ i 1))))))) + +;; read the whole request (headers + Content-Length body). Returns a string, or #f. +(define (read-request conn) + (let ((buf (foreign-alloc hs-bufsize))) + (let loop ((acc (make-bytevector 0))) + (let ((n (c-recv conn buf hs-bufsize 0))) + (if (<= n 0) + (begin (foreign-free buf) (if (> (bytevector-length acc) 0) (utf8->safe acc) #f)) + (let ((chunk (make-bytevector n))) + (do ((i 0 (+ i 1))) ((= i n)) (bytevector-u8-set! chunk i (foreign-ref 'unsigned-8 buf i))) + (let* ((acc2 (bv-append acc chunk)) + (hdr-end (bv-find-crlfcrlf acc2))) + (if (not hdr-end) + (loop acc2) + (let ((clen (content-length acc2 hdr-end))) + (if (>= (- (bytevector-length acc2) hdr-end) clen) + (begin (foreign-free buf) (utf8->safe acc2)) + (loop acc2))))))))))) +(define (bv-append a b) + (let ((out (make-bytevector (+ (bytevector-length a) (bytevector-length b))))) + (bytevector-copy! a 0 out 0 (bytevector-length a)) + (bytevector-copy! b 0 out (bytevector-length a) (bytevector-length b)) + out)) +(define (utf8->safe bv) (guard (e (#t (bytes->latin1 bv))) (utf8->string bv))) +(define (bytes->latin1 bv) (list->string (map integer->char (bytevector->u8-list bv)))) +(define (content-length bv hdr-end) + (let* ((hdrs (ascii-string-down (utf8->safe (let ((b (make-bytevector hdr-end))) + (bytevector-copy! bv 0 b 0 hdr-end) b)))) + (idx (string-search hdrs "content-length:"))) + (if (not idx) 0 + (let* ((s (+ idx (string-length "content-length:"))) + (e (let scan ((i s)) (if (or (>= i (string-length hdrs)) + (char=? (string-ref hdrs i) #\return) + (char=? (string-ref hdrs i) #\newline)) i (scan (+ i 1)))))) + (or (string->number (string-trim-ws (substring hdrs s e))) 0))))) +(define (string-search hay needle) + (let ((hl (string-length hay)) (nl (string-length needle))) + (let loop ((i 0)) (cond ((> (+ i nl) hl) #f) + ((string=? (substring hay i (+ i nl)) needle) i) + (else (loop (+ i 1))))))) +(define (string-trim-ws s) + (let* ((n (string-length s)) + (a (let lp ((i 0)) (if (and (< i n) (char-whitespace? (string-ref s i))) (lp (+ i 1)) i))) + (b (let lp ((i n)) (if (and (> i a) (char-whitespace? (string-ref s (- i 1)))) (lp (- i 1)) i)))) + (substring s a b))) + +;; --- request -> Ring map ---------------------------------------------------- +(define (split-once s ch) + (let scan ((i 0)) (cond ((>= i (string-length s)) (values s #f)) + ((char=? (string-ref s i) ch) (values (substring s 0 i) (substring s (+ i 1) (string-length s)))) + (else (scan (+ i 1)))))) +(define (req-lines s) + (let loop ((i 0) (start 0) (acc '())) + (cond ((>= i (string-length s)) (reverse (cons (substring s start i) acc))) + ((char=? (string-ref s i) #\newline) + (let ((line (if (and (> i start) (char=? (string-ref s (- i 1)) #\return)) (substring s start (- i 1)) (substring s start i)))) + (loop (+ i 1) (+ i 1) (cons line acc)))) + (else (loop (+ i 1) start acc))))) +(define (request->ring text port) + (let* ((blank (string-search text "\r\n\r\n")) + (head (if blank (substring text 0 blank) text)) + (body (if blank (substring text (+ blank 4) (string-length text)) "")) + (lines (req-lines head)) + (reqline (if (pair? lines) (car lines) "GET / HTTP/1.1")) + (parts (let lp ((i 0) (start 0) (acc '())) + (cond ((>= i (string-length reqline)) (reverse (cons (substring reqline start i) acc))) + ((char=? (string-ref reqline i) #\space) + (lp (+ i 1) (+ i 1) (cons (substring reqline start i) acc))) + (else (lp (+ i 1) start acc))))) + (method (if (pair? parts) (car parts) "GET")) + (target (if (and (pair? parts) (pair? (cdr parts))) (cadr parts) "/")) + (hdrs (let loop ((ls (if (pair? lines) (cdr lines) '())) (m (jolt-hash-map))) + (if (null? ls) m + (let-values (((k v) (split-once (car ls) #\:))) + (if v (loop (cdr ls) (jolt-assoc m (ascii-string-down (string-trim-ws k)) (string-trim-ws v))) + (loop (cdr ls) m))))))) + (let-values (((uri qs) (split-once target #\?))) + (jolt-hash-map + (keyword #f "server-port") port + (keyword #f "server-name") "127.0.0.1" + (keyword #f "remote-addr") "127.0.0.1" + (keyword #f "uri") uri + (keyword #f "query-string") (if qs qs jolt-nil) + (keyword #f "scheme") (keyword #f "http") + (keyword #f "request-method") (keyword #f (ascii-string-down method)) + (keyword #f "protocol") "HTTP/1.1" + (keyword #f "headers") hdrs + (keyword #f "body") (if (> (string-length body) 0) (host-new "StringReader" body) jolt-nil))))) + +;; --- Ring response -> bytes ------------------------------------------------- +(define (status-text code) + (cond ((= code 200) "OK") ((= code 201) "Created") ((= code 204) "No Content") + ((= code 303) "See Other") ((= code 302) "Found") ((= code 304) "Not Modified") + ((= code 400) "Bad Request") ((= code 401) "Unauthorized") ((= code 403) "Forbidden") + ((= code 404) "Not Found") ((= code 405) "Method Not Allowed") ((= code 500) "Internal Server Error") + (else "OK"))) +(define (body->string b) + (cond ((jolt-nil? b) "") + ((string? b) b) + ((or (cseq? b) (empty-list-t? b) (pvec? b)) (apply string-append (map jolt-str-render-one (seq->list (jolt-seq b))))) + (else (jolt-str-render-one b)))) +(define (response->bytes resp) + (let* ((status (let ((s (jolt-get resp (keyword #f "status")))) (if (jolt-nil? s) 200 (jnum->exact s)))) + (headers (jolt-get resp (keyword #f "headers"))) + (body (body->string (jolt-get resp (keyword #f "body")))) + (body-bv (string->utf8 body)) + (out (open-output-string))) + (display (string-append "HTTP/1.1 " (number->string status) " " (status-text status) "\r\n") out) + (when (pmap? headers) + (pmap-fold headers (lambda (k v acc) + (display (string-append (jolt-str-render-one k) ": " (jolt-str-render-one v) "\r\n") out) acc) 0)) + (display (string-append "Content-Length: " (number->string (bytevector-length body-bv)) "\r\n") out) + (display "Connection: close\r\n\r\n" out) + (bv-append (string->utf8 (get-output-string out)) body-bv))) + +(define (send-all conn bv) + (let ((len (bytevector-length bv)) (buf (foreign-alloc (max 1 (bytevector-length bv))))) + (do ((i 0 (+ i 1))) ((= i len)) (foreign-set! 'unsigned-8 buf i (bytevector-u8-ref bv i))) + (let loop ((off 0)) + (when (< off len) + (let ((n (c-send conn (+ buf off) (- len off) 0))) + (if (<= n 0) (set! off len) (loop (+ off n)))))) + (foreign-free buf))) + +;; --- the server ------------------------------------------------------------- +(define (serve-loop listen-fd handler port) + (let loop () + (let ((conn (c-accept listen-fd 0 0))) + (when (>= conn 0) + (guard (e (#t (guard (e2 (#t #f)) + (send-all conn (response->bytes + (jolt-hash-map (keyword #f "status") 500 + (keyword #f "body") "Internal Server Error")))))) + (let ((text (read-request conn))) + (when text + (let* ((req (request->ring text port)) + (resp (jolt-invoke handler req))) + (send-all conn (response->bytes resp)))))) + (c-close conn)) + (loop)))) + +;; run-server: bind+listen, spawn the accept loop on a background thread, return a +;; handle {:port :socket}. opts is a map with :port (default 3000). +(define (http-run-server handler opts) + (let* ((port (let ((p (and (pmap? opts) (jolt-get opts (keyword #f "port"))))) (if (or (not p) (jolt-nil? p)) 3000 (jnum->exact p)))) + (fd (listen-socket "127.0.0.1" port))) + (fork-thread (lambda () (serve-loop fd handler port))) + (jolt-hash-map (keyword #f "port") port (keyword #f "socket") fd))) +(define (http-stop-server server) + (when (pmap? server) + (let ((fd (jolt-get server (keyword #f "socket")))) (when (number? fd) (c-close (jnum->exact fd))))) + jolt-nil) + +(def-var! "jolt.http.server" "run-server" (lambda (handler . opt) (http-run-server handler (if (pair? opt) (car opt) (jolt-hash-map))))) +(def-var! "jolt.http.server" "stop-server" http-stop-server) +;; the ring-app example reaches the server through this adapter namespace. +(def-var! "ring-janet.adapter" "run-server" (lambda (handler . opt) (http-run-server handler (if (pair? opt) (car opt) (jolt-hash-map))))) +(def-var! "ring-janet.adapter" "stop-server" http-stop-server) diff --git a/host/chez/sqlite.ss b/host/chez/sqlite.ss new file mode 100644 index 0000000..7f80fe3 --- /dev/null +++ b/host/chez/sqlite.ss @@ -0,0 +1,134 @@ +;; sqlite.ss (jolt-90sp) — jolt.sqlite + a jdbc.core API over the system +;; libsqlite3 via Chez's FFI. The sqlite3 C API is non-variadic, so it binds +;; directly (unlike libcurl). Enough of the surface for a small app: open/close, +;; exec (DDL/DML), a prepared query returning row maps, parameter binding +;; (text/int/double), and last_insert_rowid. +;; +;; def-var!'d into jolt.sqlite AND jdbc.core (jolt-lang/db's API: connection / +;; execute! / fetch / fetch-one / last-insert-id), so an app that requires +;; jdbc.core resolves it as a baked namespace. + +(define sqlite-available? + (let loop ((names '("libsqlite3.0.dylib" "libsqlite3.so.0" "libsqlite3.dylib" "libsqlite3.so"))) + (cond ((null? names) #f) + ((guard (e (#t #f)) (load-shared-object (car names)) #t) #t) + (else (loop (cdr names)))))) + +(define sqlite3_open (foreign-procedure "sqlite3_open" (string void*) int)) +(define sqlite3_close (foreign-procedure "sqlite3_close" (void*) int)) +(define sqlite3_errmsg (foreign-procedure "sqlite3_errmsg" (void*) string)) +(define sqlite3_prepare_v2 (foreign-procedure "sqlite3_prepare_v2" (void* string int void* uptr) int)) +(define sqlite3_step (foreign-procedure "sqlite3_step" (void*) int)) +(define sqlite3_finalize (foreign-procedure "sqlite3_finalize" (void*) int)) +(define sqlite3_column_count (foreign-procedure "sqlite3_column_count" (void*) int)) +(define sqlite3_column_name (foreign-procedure "sqlite3_column_name" (void* int) string)) +(define sqlite3_column_type (foreign-procedure "sqlite3_column_type" (void* int) int)) +(define sqlite3_column_text (foreign-procedure "sqlite3_column_text" (void* int) string)) +(define sqlite3_column_int64 (foreign-procedure "sqlite3_column_int64" (void* int) integer-64)) +(define sqlite3_column_double (foreign-procedure "sqlite3_column_double" (void* int) double)) +(define sqlite3_bind_text (foreign-procedure "sqlite3_bind_text" (void* int string int iptr) int)) +(define sqlite3_bind_int64 (foreign-procedure "sqlite3_bind_int64" (void* int integer-64) int)) +(define sqlite3_bind_double (foreign-procedure "sqlite3_bind_double" (void* int double) int)) +(define sqlite3_bind_null (foreign-procedure "sqlite3_bind_null" (void* int) int)) +(define sqlite3_last_insert_rowid (foreign-procedure "sqlite3_last_insert_rowid" (void*) integer-64)) + +(define SQLITE_OK 0) (define SQLITE_ROW 100) (define SQLITE_DONE 101) +(define SQLITE_TRANSIENT -1) + +;; a connection is a jhost "sqlite-conn" wrapping the sqlite3* (as an integer addr) +(define (sql-conn db) (make-jhost "sqlite-conn" (vector db))) +(define (sql-conn-db c) (vector-ref (jhost-state c) 0)) +(define (sql-conn? x) (and (jhost? x) (string=? (jhost-tag x) "sqlite-conn"))) + +(define (sql-open path) + (unless sqlite-available? (error #f "jolt.sqlite: libsqlite3 not found")) + (let ((pp (foreign-alloc 8))) + (foreign-set! 'void* pp 0 0) + (let ((rc (sqlite3_open path pp))) + (let ((db (foreign-ref 'void* pp 0))) + (foreign-free pp) + (if (= rc SQLITE_OK) (sql-conn db) + (error #f (string-append "sqlite open failed: " path))))))) + +(define (sql-close c) (sqlite3_close (sql-conn-db c)) jolt-nil) + +(define (sql-prepare db sql) + (let ((pp (foreign-alloc 8))) + (foreign-set! 'void* pp 0 0) + (let ((rc (sqlite3_prepare_v2 db sql -1 pp 0))) + (let ((stmt (foreign-ref 'void* pp 0))) + (foreign-free pp) + (if (= rc SQLITE_OK) stmt + (error #f (string-append "sqlite prepare failed: " (sqlite3_errmsg db) " — " sql))))))) + +;; bind a jolt value to parameter i (1-based) +(define (sql-bind! stmt i v) + (cond + ((jolt-nil? v) (sqlite3_bind_null stmt i)) + ((and (number? v) (exact? v) (integer? v)) (sqlite3_bind_int64 stmt i v)) + ((number? v) (sqlite3_bind_double stmt i (inexact v))) + ((string? v) (sqlite3_bind_text stmt i v -1 SQLITE_TRANSIENT)) + (else (sqlite3_bind_text stmt i (jolt-str-render-one v) -1 SQLITE_TRANSIENT)))) + +;; read the current row as a jolt map {keyword-col -> value} +(define (sql-row stmt) + (let ((n (sqlite3_column_count stmt)) (m (jolt-hash-map))) + (do ((i 0 (+ i 1))) ((= i n) m) + (let* ((nm (sqlite3_column_name stmt i)) + (ty (sqlite3_column_type stmt i)) + (v (cond ((= ty 1) (sqlite3_column_int64 stmt i)) + ((= ty 2) (sqlite3_column_double stmt i)) + ((= ty 5) jolt-nil) + (else (sqlite3_column_text stmt i))))) + (set! m (jolt-assoc m (keyword #f nm) v)))))) + +;; run sql with params; return a vector of row maps (empty for a non-SELECT). +(define (sql-query c sql params) + (let ((db (sql-conn-db c)) (stmt (sql-prepare (sql-conn-db c) sql))) + (let bind ((i 1) (ps params)) (when (pair? ps) (sql-bind! stmt i (car ps)) (bind (+ i 1) (cdr ps)))) + (let loop ((rows '())) + (let ((rc (sqlite3_step stmt))) + (cond + ((= rc SQLITE_ROW) (loop (cons (sql-row stmt) rows))) + ((= rc SQLITE_DONE) (sqlite3_finalize stmt) (apply jolt-vector (reverse rows))) + (else (let ((msg (sqlite3_errmsg db))) (sqlite3_finalize stmt) + (error #f (string-append "sqlite step failed: " msg))))))))) + +;; --- jolt.sqlite (a small direct API) --------------------------------------- +(define (sqlite-vec-args a) (if (and (pair? a) (or (pvec? (car a)) (cseq? (car a)) (empty-list-t? (car a)))) + (seq->list (car a)) a)) +(def-var! "jolt.sqlite" "open" (lambda (path) (sql-open path))) +(def-var! "jolt.sqlite" "close" (lambda (c) (sql-close c))) +(def-var! "jolt.sqlite" "query" (lambda (c sql . ps) (sql-query c sql (sqlite-vec-args ps)))) +(def-var! "jolt.sqlite" "execute!" (lambda (c sql . ps) (sql-query c sql (sqlite-vec-args ps)) jolt-nil)) +(def-var! "jolt.sqlite" "last-insert-id" (lambda (c) (sqlite3_last_insert_rowid (sql-conn-db c)))) +(def-var! "jolt.sqlite" "available?" (lambda () (if sqlite-available? #t #f))) + +;; --- jdbc.core (jolt-lang/db API) ------------------------------------------- +;; A honeysql sql-vector is [sql & params]; jdbc fns take that directly. The +;; connection string is "sqlite:" (or "jdbc:sqlite:"). +(define (jdbc-strip-scheme url) + (let ((u (jolt-str-render-one url))) + (let strip ((prefixes '("jdbc:sqlite:" "sqlite:"))) + (cond ((null? prefixes) u) + ((and (>= (string-length u) (string-length (car prefixes))) + (string=? (substring u 0 (string-length (car prefixes))) (car prefixes))) + (substring u (string-length (car prefixes)) (string-length u))) + (else (strip (cdr prefixes))))))) +(define (jdbc-sqlvec v) ; [sql & params] -> (values sql params-list) + (let ((items (cond ((pvec? v) (seq->list v)) + ((or (cseq? v) (empty-list-t? v)) (seq->list v)) + ((string? v) (list v)) + (else '())))) + (values (if (pair? items) (jolt-str-render-one (car items)) "") + (if (pair? items) (cdr items) '())))) +(def-var! "jdbc.core" "connection" (lambda (url) (sql-open (jdbc-strip-scheme url)))) +(def-var! "jdbc.core" "execute!" + (lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) (sql-query c sql ps) jolt-nil))) +(def-var! "jdbc.core" "fetch" + (lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) (sql-query c sql ps)))) +(def-var! "jdbc.core" "fetch-one" + (lambda (c sqlvec) (let-values (((sql ps) (jdbc-sqlvec sqlvec))) + (let ((rows (sql-query c sql ps))) + (if (> (pvec-count rows) 0) (pvec-nth-d rows 0 jolt-nil) jolt-nil))))) +(def-var! "jdbc.core" "last-insert-id" (lambda (c) (sqlite3_last_insert_rowid (sql-conn-db c))))