diff --git a/assert.ss b/assert.ss index 7eaaded..cc5dc1c 100644 --- a/assert.ss +++ b/assert.ss @@ -4,7 +4,7 @@ (export #t) (import - :std/logger :std/sugar + (only-in :std/logger warn-and-err) ./base ./error) (defrules assert-comparison! () diff --git a/base.ss b/base.ss index 6e32328..07a16f4 100644 --- a/base.ss +++ b/base.ss @@ -154,11 +154,6 @@ (λ (data nil cons) ((reduce (map data (curry curry cons)) identity compose) nil))) - - -;;;; Stupid error non-handling -(defrule (ignore-errors form ...) (with-catch (λ (_) #f) (λ () form ...))) - ;;;; Basic error cases ;; Use Undefined where the language requires you to cover a case that is actually diff --git a/build.ss b/build.ss index dbfd717..59dd044 100755 --- a/build.ss +++ b/build.ss @@ -4,7 +4,9 @@ (import :gerbil/expander (only-in :gerbil/runtime/init add-load-path) - :std/getopt + #;(only-in :std/error dump-stack-trace?) ;; Only in v0.19 + (only-in :std/cli/getopt rest-arguments option) + (only-in :std/cli/multicall define-entry-point call-entry-point define-multicall-main) :std/misc/list :std/misc/process :std/source @@ -30,31 +32,31 @@ srcdir name: "Gerbil-utils" spec: files) +(define-multicall-main) -(def (build-nix . opts) +(define-entry-point (nix . opts) + (help: "build using nix-build" + getopt: [(rest-arguments 'nix-options help: "options to pass on to nix")]) (clan/building#create-version-file) (run-process ["nix-build" opts ...]) (void)) -(clan/multicall#register-entry-point - build-nix name: "nix" help: "build using nix-build" - getopt: [(rest-arguments 'nix-options help: "options to pass on to nix")]) -(def (build-docker . opts) +(define-entry-point (docker . opts) + (help: "build a Gerbil NixOS docker image" + getopt: [(rest-arguments 'docker-options help: "options to pass on to docker")]) (void (run-process ["./scripts/make-docker-image.ss" opts ...] stdin-redirection: #f stdout-redirection: #f))) -(clan/multicall#register-entry-point - build-docker name: "docker" help: "build a Gerbil NixOS docker image" - getopt: [(rest-arguments 'docker-options help: "options to pass on to docker")]) -(def (build-nixpkgs nixpkgs-file: (nixpkgs-file #f)) +(define-entry-point (nixpkgs nixpkgs-file: (nixpkgs-file #f)) + (help: "build all gerbil packages and their dependencies" + getopt: [(option 'nixpkgs-file "-f" "--file" help: "path or url for nixpkgs")]) (void (run-process ["nix-env" "--show-trace" (when/list nixpkgs-file ["--file" nixpkgs-file])... "-iA" "gerbil-unstable" "gerbilPackages-unstable"]))) -(clan/multicall#register-entry-point - build-nixpkgs name: "nixpkgs" help: "build all gerbil packages and their dependencies" - getopt: [(option 'nixpkgs-file "-f" "--file" help: "path or url for nixpkgs")]) -(def (publish-nixpkgs nixpkgs-file: (nixpkgs-file #f)) +(define-entry-point (publish-nixpkgs nixpkgs-file: (nixpkgs-file #f)) + (help: "publish all gerbil packages and their dependencies to cachix" + getopt: [(option 'nixpkgs-file "-f" "--file" help: "path or url for nixpkgs")]) (clan/base#!> (run-process ["nix" "path-info" (when/list nixpkgs-file ["--file" nixpkgs-file])... @@ -63,11 +65,5 @@ (cut cons* "cachix" "push" "mukn" <>) (cut run-process/batch <>) void)) -(clan/multicall#register-entry-point - publish-nixpkgs name: "publish" - help: "publish all gerbil packages and their dependencies to cachix" - getopt: [(option 'nixpkgs-file "-f" "--file" help: "path or url for nixpkgs")]) -(def main clan/multicall#call-entry-point) -(clan/multicall#current-program "build.ss") -(clan/exit#backtrace-on-abort? #t) +#;(dump-stack-trace? #t) ;; Only in v0.19 diff --git a/building.ss b/building.ss index 3c46357..808405b 100644 --- a/building.ss +++ b/building.ss @@ -2,15 +2,26 @@ (export #t) (import - :gerbil/gambit + (only-in :gerbil/gambit pretty-print shell-command) (only-in :gerbil/runtime/init add-load-path) - :std/format :std/getopt :std/iter :std/make :std/source - :std/misc/list :std/misc/path :std/misc/ports :std/misc/process :std/misc/string - :std/pregexp :std/srfi/1 :std/srfi/13 :std/sugar - ./exit ./filesystem ./git-fu ./multicall ./nix-fu - ./path-config ./ports ./versioning) - -(def default-exclude '("main.ss")) + (only-in :std/cli/getopt flag) + (only-in :std/cli/multicall define-entry-point set-default-entry-point! + current-program define-multicall-main) + #;(only-in :std/error dump-stack-trace?) ;; only in v0.19 + (only-in :std/make make) + (only-in :std/source this-source-file) + (only-in :std/misc/list when/list) + (only-in :std/misc/path path-maybe-normalize subpath path-extension-is?) + (only-in :std/misc/string string-trim-eol) + (only-in :std/srfi/1 lset-difference) + (only-in ./filesystem find-files path-is-script?) + (only-in ./git-fu update-version-from-git) + (only-in ./nix-fu gerbil-is-nix?) + (only-in ./path-config set-path-config-root! + application-source-directory application-home-directory) + (only-in ./ports set-current-ports-encoding-standard-unix!)) + +(def default-exclude '("main.ss" "manifest.ss")) (def default-exclude-dirs '("run" "t" ".git" "_darcs" ".gerbil")) (def (all-gerbil-modules exclude: (exclude default-exclude) @@ -58,9 +69,9 @@ ((ctx args ...) (begin (def here (this-source-file ctx)) - (with-id ctx (main) - (define-multicall-main ctx) - (%set-build-environment! here args ...))))) + ;;(with-id ctx (main) (def main call-entry-point)) + (define-multicall-main ctx) + (%set-build-environment! here args ...)))) (def ($ cmd) (match (shell-command cmd #t) @@ -135,4 +146,5 @@ (def optimize? (not no-optimize)) (pretty-print (build-spec tcc: tcc optimize: optimize?))) -(backtrace-on-abort? #f) +#;(dump-stack-trace? #f) ;; Only in v0.19 + diff --git a/call-limiter.ss b/call-limiter.ss index a968782..94101fe 100644 --- a/call-limiter.ss +++ b/call-limiter.ss @@ -20,10 +20,22 @@ call-limiter-loop) (import - :std/actor :std/format :std/getopt :std/logger - :std/misc/list :std/misc/number :std/misc/pqueue :std/sort - :std/srfi/1 :std/srfi/19 :std/sugar - ./base ./concurrency ./error ./timestamp ./multicall) + :std/actor + :std/cli/getopt + :std/cli/multicall + :std/format + :std/logger + :std/misc/list + :std/misc/number + :std/misc/pqueue + :std/sort + :std/srfi/1 + :std/srfi/19 + :std/sugar + ./base + ./concurrency + ./error + ./timestamp) (deflogger clan) diff --git a/cli.ss b/cli.ss index ba4817c..a91606c 100644 --- a/cli.ss +++ b/cli.ss @@ -1,8 +1,10 @@ (export #t) (import - :std/getopt :std/srfi/13 :std/sugar - ./exit ./hash ./json) + :std/srfi/13 + (only-in :std/cli/getopt flag) + (only-in ./hash hash-removed) + (only-in ./json read-file-json json<-port json<-string)) ;; Given a string argument designating a JSON object ;; Json <- String @@ -22,4 +24,4 @@ [(flag 'backtrace "--backtrace" help: "enable backtraces for debugging purposes")]) (def process-opts/backtrace - [(lambda (opt) (backtrace-on-abort? (hash-removed opt 'backtrace)))]) + [(lambda (opt) '(dump-stack-trace? (hash-removed opt 'backtrace)))]) ;; Only in v0.19 diff --git a/exit.ss b/exit.ss deleted file mode 100644 index fe389f7..0000000 --- a/exit.ss +++ /dev/null @@ -1,57 +0,0 @@ -;; -*- Gerbil -*- -;;;; Support for building a single multicall binary that has all the fricfrac functionality. - -(export #t) - -(import - :gerbil/gambit - :std/format :std/misc/list :std/misc/repr :std/sugar - ./base ./exception ./ports ./versioning) - -(defrule (eval-print-exit body ...) (call-print-exit (λ () body ...))) - -;; Return a magic value that will be not be printed but will return an error code. -;; (void) is silent success, because it's what successful side-effecting functions return. -;; (values) is failure, because it's the other naturally silent return thing, and it's abnormal enough. -(def (silent-exit (bool #t)) - (if bool (void) (values))) - -;; Execute a function, print the result (if application), and exit with an according value. -;; -;; (void) prints nothing and counts as false. #f is printed and counts as false. -;; (values) prints nothing and counts as true. All other values are printed and count as true. -;; If you want to print #f and return true, then print it then return (values). -;; -;; True is returned as exit code 0, false as exit code 1. -;; Any uncaught exception will be printed then trigger an exit with code 2. -(def (call-print-exit fun) - (with-catch - (λ (x) (ignore-errors (eprintf "~s~%" x)) (exit 2)) - (call/values - fun - (λ vs - (unless (equal? vs [(void)]) - (for-each prn vs)) - (ignore-errors (force-current-outputs)) - (exit (if (or (null? vs) (and (length=n? vs 1) (not (car vs)))) 1 0)))))) - -(def abort-on-error? (make-parameter #t)) -(def backtrace-on-abort? (make-parameter #t)) - -(def (call-with-abort-on-error thunk) - (with-catch/cont - (lambda (e k) - (if (abort-on-error?) - (let (port (current-error-port)) - (ignore-errors (force-output)) - (show-version complete: #t port: port) - (when (backtrace-on-abort?) - (fprintf port "In thread ~a:\n" (thread-name (current-thread))) - (display-continuation-backtrace k port #t #t 20 20)) - (display-exception-in-context e k port) - (force-output port) - (exit 2)) - (raise e))) - thunk)) - -(defrule (with-abort-on-error () body ...) (call-with-abort-on-error (lambda () body ...))) diff --git a/gerbil-nix-env.sh b/gerbil-nix-env.sh index 2286e58..2f426aa 100755 --- a/gerbil-nix-env.sh +++ b/gerbil-nix-env.sh @@ -1,4 +1,4 @@ -# gerbil-nix-env.sh -*- Shell -*- +# gerbil-nix-env.sh -*- shell-mode -*- # Copyright 2017 Francois-Rene Rideau # This file is published under both LGPLv2.1 and Apache 2.0 licenses. # @@ -49,13 +49,15 @@ export NIX_GERBIL_LOADPATH=$HOME/.nix-profile/gerbil:/nix/var/nix/profiles/defau export GERBIL_LOADPATH=$NIX_GERBIL_LOADPATH # Get the flags for compiling and linking against openssl and other libraries. -eval "$(nix-shell '' --pure --attr ${GERBIL_PACKAGE} --command \ - 'echo "export \ - NIX_SHELL_PATH=\"$PATH\" \ - NIX_LDFLAGS=\"$NIX_LDFLAGS\" \ - NIX_BINTOOLS=\"$NIX_BINTOOLS\" \ - NIX_CC=\"$NIX_CC\" \ - NIX_CFLAGS_COMPILE=\"$NIX_CFLAGS_COMPILE\""')" +function gerbil_compile_env () { + eval "$(nix-shell '' --pure --attr ${GERBIL_PACKAGE} --command \ + 'echo "export \ + NIX_SHELL_PATH=\"$PATH\" \ + NIX_LDFLAGS=\"$NIX_LDFLAGS\" \ + NIX_BINTOOLS=\"$NIX_BINTOOLS\" \ + NIX_CC=\"$NIX_CC\" \ + NIX_CFLAGS_COMPILE=\"$NIX_CFLAGS_COMPILE\""')" +} : ${ORIG_PATH:=$PATH} export ORIG_PATH diff --git a/git-fu.ss b/git-fu.ss index f8f9b6b..9d58340 100644 --- a/git-fu.ss +++ b/git-fu.ss @@ -17,7 +17,7 @@ :std/source :std/srfi/1 :std/sugar - ./base ./exit ./filesystem ./io ./list + ./base ./filesystem ./io ./list ./path-config ./ports ./rpm-versioning ./syntax) ;; TODO: move that to another file? diff --git a/io.ss b/io.ss index 0480b54..805e943 100644 --- a/io.ss +++ b/io.ss @@ -1,11 +1,9 @@ (export #t) (import - :gerbil/gambit - :std/assert - :std/misc/bytes - :std/misc/number - :std/sugar - ./base) + (only-in :std/assert assert!) + (only-in :std/misc/bytes big uint->u8vector u8vector->uint + u8vector-u16-set!) + (only-in :std/misc/number uint-length-in-u8)) ;;(def (write-u8vector v p) (write-subu8vector v 0 (u8vector-length v) p)) ;;(def (read-u8vector v p) (def l (u8vector-length v)) (read-subu8vector v 0 l p l)) @@ -22,7 +20,7 @@ (unmarshal-n-u8 size port)) ;; Read a given number of bytes, even if the number is zero -;; : Bytes <- Nat In +;; : Bytes <- UInt In (def (unmarshal-n-u8 size port) (if (zero? size) #u8() @@ -34,7 +32,7 @@ (def (marshal-uint16 n port) (assert! (<= 0 n 65535)) (def u8vector (make-u8vector 2 0)) - (bytevector-u16-set! u8vector 0 n big) + (u8vector-u16-set! u8vector 0 n big) (write-u8vector u8vector port)) ;; : <- U8vector Out @@ -48,27 +46,29 @@ ;; : ('a <- U8vector) <- ('a <- In) (def (<-u8vector<-unmarshal unmarshal) - (nest (lambda (u8vector)) (call-with-input-u8vector u8vector) (lambda (port)) - (let ((v (unmarshal port))) - (assert! (not (eof-object? v))) - (assert! (eof-object? (read-u8 port))) - v))) + (lambda (u8vector) + (call-with-input-u8vector u8vector + (lambda (port) + (let ((v (unmarshal port))) + (assert! (not (eof-object? v))) + (assert! (eof-object? (read-u8 port))) + v))))) ;; : (<- 'a Out) <- (U8vector <- 'a) (def (marshal<-u8vector<- u8vector<-) (lambda (x port) (write-u8vector (u8vector<- x) port))) -;; : ('a <- In) <- ('a <- U8vector) Nat +;; : ('a <- In) <- ('a <- U8vector) UInt (def (unmarshal<-<-u8vector <-u8vector n) (lambda (port) (<-u8vector (unmarshal-n-u8 n port)))) -;; : Nat <- Nat In -(def (read-nat-u8vector length-in-u8 in) - (u8vector->nat (unmarshal-n-u8 length-in-u8 in))) +;; : UInt <- UInt In +(def (read-uint-u8vector length-in-u8 in) + (u8vector->uint (unmarshal-n-u8 length-in-u8 in))) -;; : <- Int Nat+ Out -(def (write-nat-u8vector x length-in-u8 out) - (write-u8vector (nat->u8vector x length-in-u8) out)) +;; : <- Int UInt+ Out +(def (write-uint-u8vector x length-in-u8 out) + (write-u8vector (uint->u8vector x big length-in-u8) out)) ;; Encoding and decoding integers into self-delimited byte streams, preserving lexicographic order ;; supposing the first byte is compared signed and the rest unsigned. @@ -79,81 +79,81 @@ (cond ((< x 64) x) ((< x 127) (let* ((l (- x 63)) - (n (read-nat-u8vector l in))) + (n (read-uint-u8vector l in))) (assert! (>= n 64)) - (assert! (= (integer-length-in-u8 n) l)) + (assert! (= (uint-length-in-u8 n) l)) n)) (else ; (= x 127) (let* ((l (read-varint in)) - (n (read-nat-u8vector l in))) + (n (read-uint-u8vector l in))) (assert! (>= l 64)) - (assert! (= (integer-length-in-u8 n) l)) + (assert! (= (uint-length-in-u8 n) l)) n))) (cond ((>= x 192) (- x 256)) ((> x 128) (let* ((l (- 192 x)) - (n (bitwise-ior (arithmetic-shift -1 l) (read-nat-u8vector l in)))) + (n (bitwise-ior (arithmetic-shift -1 l) (read-uint-u8vector l in)))) (assert! (< n -64)) - (assert! (= l (integer-length-in-u8 n))) + (assert! (= l (uint-length-in-u8 n))) n)) (else ; (= x 128) (let* ((l (- (read-varint in))) - (n (bitwise-ior (arithmetic-shift -1 l) (read-nat-u8vector l in)))) + (n (bitwise-ior (arithmetic-shift -1 l) (read-uint-u8vector l in)))) (assert! (> l 63)) - (assert! (= l (integer-length-in-u8 n))) + (assert! (= l (uint-length-in-u8 n))) n)))))) ;; : <- Int Out (def (write-varint n out) (if (negative? n) (if (>= n -64) (write-u8 (bitwise-and 255) out) - (let ((l (integer-length-in-u8 n))) + (let ((l (uint-length-in-u8 n))) (if (<= 63) (begin (write-u8 (- 192 l) out) - (write-nat-u8vector n l out)) + (write-uint-u8vector n l out)) (begin (write-u8 128 out) (write-varint (- l) out) - (write-nat-u8vector n l out))))) + (write-uint-u8vector n l out))))) (if (<= n 63) (write-u8 n out) - (let ((l (integer-length-in-u8 n))) + (let ((l (uint-length-in-u8 n))) (if (<= l 63) (begin (write-u8 (+ l 63) out) - (write-nat-u8vector n l out)) + (write-uint-u8vector n l out)) (begin (write-u8 127 out) (write-varint l out) - (write-nat-u8vector n l out))))))) + (write-uint-u8vector n l out))))))) ;; Encoding and decoding natural integers into self-delimited byte streams, preserving lexicographic order. -;; : Nat <- In -(def (read-varnat in) +;; : UInt <- In +(def (read-varuint in) (let ((x (read-u8 in))) (cond ((< x 128) x) ((< x 255) (let* ((l (- x 127)) - (n (read-nat-u8vector l in))) + (n (read-uint-u8vector l in))) (assert! (> n 127)) - (assert! (= (integer-length-in-u8 n) l)) + (assert! (= (uint-length-in-u8 n) l)) n)) (else ; (= x 255) - (let* ((l (read-varnat in)) - (n (read-nat-u8vector l in))) + (let* ((l (read-varuint in)) + (n (read-uint-u8vector l in))) (assert! (> l 127)) - (assert! (= (integer-length-in-u8 n) l)) + (assert! (= (uint-length-in-u8 n) l)) n))))) -;; : <- Nat Out -(def (write-varnat n out) +;; : <- UInt Out +(def (write-varuint n out) (if (<= n 127) (write-u8 n out) - (let ((l (integer-length-in-u8 n))) + (let ((l (uint-length-in-u8 n))) (if (<= l 127) (begin (write-u8 (+ l 127) out) - (write-nat-u8vector n l out)) + (write-uint-u8vector n l out)) (begin (write-u8 255 out) - (write-varnat l out) - (write-nat-u8vector n l out)))))) + (write-varuint l out) + (write-uint-u8vector n l out)))))) diff --git a/multicall.ss b/multicall.ss deleted file mode 100644 index e5fda74..0000000 --- a/multicall.ss +++ /dev/null @@ -1,131 +0,0 @@ -;; -*- Gerbil -*- -;;;; Support for building a single multicall binary that has all the fricfrac functionality. - -(export #t) - -(import - :std/format :std/generic :std/getopt :std/iter - :std/misc/hash :std/misc/list :std/misc/list-builder :std/misc/number - :std/sort :std/source :std/srfi/13 :std/stxutil :std/sugar - (for-syntax :std/stxutil) - ./base ./exit ./list ./shell ./versioning) - -(def current-program (make-parameter [])) -(def entry-points (make-hash-table)) - -(def (current-program-string (program (current-program))) - (string-join (reverse (flatten-pair-tree program)) " ")) - -(defgeneric getopt-spec - (lambda (spec) - (cond - ((list? spec) spec) - ((nat? spec) (for/collect ((i (in-iota spec 1))) (argument (number->string i)))) - ((not spec) [(rest-arguments "rest")]) - (else (error "Bad getopt-spec"))))) - -(defgeneric call-with-processed-command-line - (lambda (processor command-line function) - (cond - ((getopt? processor) - (call-with-getopt-parse processor (getopt-parse processor command-line) function)) - ((list? processor) - (call-with-processed-command-line (apply getopt processor) command-line function))))) - -(defstruct entry-point (name function help getopt) transparent: #t) - -(def (gopt->positional-names gopt) - (def names '()) - (def rest-name #f) - (def argkey |std/getopt#!top-key|) - (for-each (lambda (arg) - (cond - ((or (|std/getopt#!reqarg?| arg) (|std/getopt#!optarg?| arg)) - (push! (argkey arg) names)) - ((|std/getopt#!rest?| arg) (set! rest-name (argkey arg))))) - (|std/getopt#!getopt-args| gopt)) - (values (reverse names) rest-name)) - -(def (getopt-parse->positional-arguments! gopt h) - (defvalues (names rest-name) (gopt->positional-names gopt)) - (def (extract n) (begin0 (hash-get h n) (hash-remove! h n))) - (def positional (map extract names)) - (def rest (when/list rest-name (extract rest-name))) - (append positional rest)) - -(def (as-stringfunction-arguments gopt h) - (def positionals (getopt-parse->positional-arguments! gopt h)) - (append positionals - (foldr (lambda (kv l) (cons* (make-keyword (car kv)) (cdr kv) l)) '() - (hash->list/sort h as-stringfunction-arguments gopt hash))) - -(def (entry-points-getopt-spec (h entry-points)) - (for/collect (([name . e] (hash->list/sort h as-stringrequest-url url)) - (nonce (random-bytes 16)) - (nonce64 (base64-encode nonce)) - (headers (cons* ["Upgrade" . "websocket"] - ["Connection" . "Upgrade"] - ["Sec-WebSocket-Key" . nonce64] - ["Sec-WebSocket-Version" . 13] - (or headers []))) - (req (http-get url - redirect: redirect - headers: headers - cookies: cookies - params: params))) - (try - (let (status (request-status req)) - (unless (##fx= status 101) - (raise-io-error 'open-websocket-client - "Unexpected server response" - url status))) - - (let* ((rheaders (request-headers req)) - (Connection (assoc "Connection" rheaders)) - (Upgrade (assoc "Upgrade" rheaders)) - (Sec-WebSocket-Accept (assoc "Sec-Websocket-Accept" rheaders)) - (Sec-WebSocket-Extensions (assoc "Sec-Websocket-Extensions" rheaders)) - (Sec-WebSocket-Protocol (assoc "Sec-Websocket-Protocol" rheaders))) - - (unless (and Connection (equal? (string-downcase (cdr Connection)) "upgrade")) - (raise-io-error 'open-websocket-client - "Bad server response; no connection upgrade" - url Connection)) - - (unless (and Upgrade (equal? (string-downcase (cdr Upgrade)) "websocket")) - (raise-io-error 'open-websocket-client - "Bad server response; no websocket upgrade" - url Upgrade)) - - (let* ((accept64 (and Sec-WebSocket-Accept (cdr Sec-WebSocket-Accept))) - (digest (make-digest digest::sha1)) - (_ (digest-update! digest (string->utf8 nonce64))) - (_ (digest-update! digest (string->utf8 wsmagic))) - (verify (digest-final! digest)) - (verify64 (base64-encode verify))) - (unless (equal? accept64 verify64) - (raise-io-error 'open-websocket-client - "Bad server response; nonce verification failure" - url nonce64 accept64 verify64))) - - (when Sec-WebSocket-Extensions - (raise-io-error 'open-websocket-client - "Bad server response; includes unsupported exensions" - url Sec-WebSocket-Extensions)) - - (when Sec-WebSocket-Protocol - (let* ((proto (cdr Sec-WebSocket-Protocol)) - (uproto (assoc "Sec-WebSocket-Protocol" headers)) - (uproto (string-split (and uproto (cdr uproto)) #\,))) - (unless (member proto uproto) - (raise-io-error 'open-websocket-client - "Bad server response; unexpected protocol" - url Sec-WebSocket-Protocol))))) - - (make-websocket-client (request-reader req)) - - (catch (e) - (request-close req) - (raise e))))) - -(def (url->request-url url) - (let* (colon (string-index url #\:)) - (if colon - (let (scheme (substring url 0 colon)) - (case scheme - (("ws") - (string-append "http" (substring url colon (string-length url)))) - (("wss") - (string-append "https" (substring url colon (string-length url)))) - (else url))) - url))) - -(defstruct websocket (port rd wr cs q mx cv) - constructor: :init! - final: #t) - -(defmethod {:init! websocket} - (lambda (self port) - (struct-instance-init! self port - #f #f #f ; reader writer closed status - (make-queue) - (make-mutex) - (make-condition-variable)))) - -(def (make-websocket-client port) - (start-logger!) - (let* ((ws (make-websocket port)) - (reader (spawn/name 'websocket-reader websocket-reader ws)) - (writer (spawn/name 'websocket-writer websocket-writer ws))) - (set! (websocket-rd ws) - reader) - (set! (websocket-wr ws) - writer) - ws)) - -(def (websocket-send ws bytes type) - (cond - ((not (u8vector? bytes)) - (error "Bad argument; expected u8vector" bytes)) - ((not (or (eq? type 'binary) - (eq? type 'text))) - (error "Bad argument; expected 'binary or 'text" type)) - ((websocket-cs ws) - => (lambda (how) - (raise-io-error 'websocket-send "Websocket has been closed" ws how))) - (else - (thread-send (websocket-wr ws) (cons type bytes))))) - -(def (websocket-write ws obj) - (cond - ((string? obj) - (websocket-send ws (string->utf8 obj) 'text)) - ((u8vector? obj) - (websocket-send ws obj 'binary)) - (else - (error "Bad argument; expected bytes or string" obj)))) - -(def (websocket-recv ws (timeo #f) (raise? #t)) - (let (timeo (make-timeout timeo)) - (with ((websocket _ _ _ _ q mx cv) ws) - (let lp () - (mutex-lock! mx) - (cond - ((not (queue-empty? q)) - (with ([type . data] (dequeue! q)) - (mutex-unlock! mx) - (values data type))) - ((websocket-cs ws) - => (lambda (how) - (mutex-unlock! mx) - (if raise? - (raise-io-error 'websocket-recv "Websocket has been closed" ws how) - #!eof))) - ((mutex-unlock! mx cv timeo) - (lp)) - (raise? - (raise-timeout 'websocket-recv "timeout" ws)) - (else #f)))))) - -(def (websocket-read ws (timeo #f) (raise? #f)) - (match (websocket-recv ws timeo raise?) - ((values data 'binary) - data) - ((values data 'text) - (utf8->string data)) - (r r))) - -(def (websocket-close ws (how 1000) hard: (hard? #f)) - (with ((websocket port _ writer cs q mx cv) ws) - (when hard? - (close-port port)) - (unless cs - (mutex-lock! mx) - (if (websocket-cs ws) - (mutex-unlock! mx) - (let (how (if hard? 'abort how)) - (set! (websocket-cs ws) how) - (thread-send writer (cons 'close how)) - (condition-variable-broadcast! cv) - (mutex-unlock! mx)))))) - -;; Base Framing Protocol [rfc6455] -;; -;; 0 1 2 3 -;; 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 -;; +-+-+-+-+-------+-+-------------+-------------------------------+ -;; |F|R|R|R| opcode|M| Payload len | Extended payload length | -;; |I|S|S|S| (4) |A| (7) | (16/64) | -;; |N|V|V|V| |S| | (if payload len==126/127) | -;; | |1|2|3| |K| | | -;; +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + -;; | Extended payload length continued, if payload len == 127 | -;; + - - - - - - - - - - - - - - - +-------------------------------+ -;; | |Masking-key, if MASK set to 1 | -;; +-------------------------------+-------------------------------+ -;; | Masking-key (continued) | Payload Data | -;; +-------------------------------- - - - - - - - - - - - - - - - + -;; : Payload Data continued ... : -;; + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + -;; | Payload Data continued ... | -;; +---------------------------------------------------------------+ -;; - -(def max-frame-size (expt 2 20)) ; 1MB -(def max-message-size (expt 2 20)) ; 1MB - -(def (set-websocket-max-frame-size! sz) - (set! max-frame-size sz)) - -(def (set-websocket-max-message-size! sz) - (set! max-message-size sz)) - -;; reader thread -- reads frames, assembles messages and enques them -;; in the websocket queue. -(def (websocket-reader ws) - (def buf (make-u8vector 1024)) - - (def (skip-to-eof port) - (let (len (u8vector-length buf)) - (let lp () - (let (rd (read-subu8vector buf 0 len port)) - (when (##fx= len rd) - (lp)))))) - - (def (skip-payload port plen) - (let (len (u8vector-length buf)) - (let lp ((need plen)) - (cond - ((##fxzero? need)) - ((##fx<= need len) - (let (rd (read-subu8vector buf 0 need port)) - (when (##fx< rd need) - (read-eof)))) - (else - (let (rd (read-subu8vector buf 0 len port)) - (if (##fx< rd len) - (read-eof) - (lp (##fx- need len))))))))) - - ;; => (values fin opcode mask plen) - (def (read-header port) - (let* ((b0 (read-u8 port)) - (_ (when (eof-object? b0) - (read-eof))) - (b1 (read-u8 port)) - (_ (when (eof-object? b1) - (read-eof)))) - (values (##fxand b0 #x80) - (##fxand b0 #x0f) - (##fxand b1 #x80) - (##fxand b1 #x7f)))) - - (def (read-payload port plen) - (if (##fxzero? plen) - '#u8() - (let* ((data (make-u8vector plen)) - (rd (read-subu8vector data 0 plen port))) - (if (##fx< rd plen) - (read-eof) - data)))) - - (def (read-u16 port) - (let* ((b0 (read-u8 port)) - (_ (when (eof-object? b0) - (read-eof))) - (b1 (read-u8 port)) - (_ (when (eof-object? b1) - (read-eof)))) - (##fxior (##fxarithmetic-shift b0 8) b1))) - - (def (read-u64 port) - (let (rd (read-subu8vector buf 0 8 port)) - (if (##fx= rd 8) - (let lp ((k 0) (r 0)) - (if (##fx< k 8) - (lp (##fx+ k 1) - (bitwise-ior (arithmetic-shift r 8) - (##u8vector-ref buf k))) - r)) - (read-eof)))) - - (def (read-eof) - (raise 'eof)) - - (with ((websocket port _ writer _ q mx cv) ws) - (def (receive type data) - (mutex-lock! mx) - (if (websocket-cs ws) - (begin - ;; the websocket has been closed; abort and close the input port. - ;; strictly speaking this is out of spec, as we are supposed to - ;; wait for a close from the server; however: - ;; - we are no longer interested in new messages. - ;; - buggy servers in the wild may take a long time to respond - ;; to the close, if at all, while still sending us stuff that - ;; fills the queue with no backpressure and keeps the socket - ;; open. - ;; - so damn the torpedoes, just abort. - (mutex-unlock! mx) - (raise 'abort)) - (begin - (enqueue! q (cons type data)) - (condition-variable-broadcast! cv) - (mutex-unlock! mx)))) - - (try - (let lp ((type #f) (frags [])) - (let* (((values fin opcode mask plen) - (read-header port)) - (plen - (case plen - ((126) (read-u16 port)) - ((127) (read-u64 port)) - (else plen)))) - (cond - ((not (##fxzero? mask)) - (warnf "server sent masked frame; closing websocket connection") - (websocket-close ws 1002) - (skip-to-eof port)) - ((> plen max-frame-size) - (warnf "frame size ~a exceeds max frame size; closing websocket connection" - plen) - (websocket-close ws 1009) - (skip-to-eof port)) - (else - (case opcode - ((#x0) ; continuation frame - (let (dlen (foldl ##fx+ plen (map u8vector-length frags))) - (cond - ((not type) - (warnf "unexpected continuation frame from server; closing websocket connection") - (websocket-close ws 1002) - (skip-to-eof port)) - ((##fx> dlen max-message-size) - (warnf "message length ~a exceeds max message size; closing websocket connection" - dlen) - (websocket-close ws 1009) - (skip-to-eof port)) - ((##fxzero? fin) - (if (##fxzero? plen) - (lp type frags) ; empty frame, skip - (let (data (read-payload port plen)) - (lp type (cons data frags))))) - (else - (let* ((data (read-payload port plen)) - (message (u8vector-concatenate (reverse (cons data frags))))) - (receive type message) - (lp #f [])))))) - ((#x1 #x2) ; text or binary frame - (let (xtype (if (##fx= opcode #x1) 'text 'binary)) - (cond - (type ; receiving cont frames - (warnf "unexpected frame ~x from server; closing websocket connection" - opcode) - (websocket-close ws 1002) - (skip-to-eof port)) - ((##fxzero? fin) ; first fragment - (let (data (read-payload port plen)) - (lp xtype [data]))) - (else ; unfragmented msg - (let (data (read-payload port plen)) - (receive xtype data) - (lp #f [])))))) - ((#x8) ; connection close - (let (how (read-u16 port)) - (websocket-close ws how) - (skip-to-eof port))) - ((#x9) ; ping - (let (data (read-payload port plen)) - (thread-send writer (cons 'pong data)) - (lp type frags))) - ((#xA) ; pong - (skip-payload port plen) - (lp type frags)) - (else - (warnf "unexpected frame ~x from server; closing websocket connection" - opcode) - (websocket-close ws 1002) - (skip-to-eof port))))))) - (catch (e) - (case e - ((eof) - (websocket-close ws 'eof)) - ((abort) - (void)) - (else - (errorf "unhandled exception: ~a" e) - (websocket-close ws 'abort) - (raise e))) - e) - (finally - (with-catch void (cut close-input-port port)))))) - -(def (websocket-writer ws) - (def buf (make-u8vector 65535)) - (def mask-bytes (make-u8vector 4)) - - (def (write-u16 plen port) - (write-u8 (##fxand (##fxarithmetic-shift plen -8) #xff) port) - (write-u8 (##fxand plen #xff) port)) - - (def (write-payload mask data start end port) - (let lp ((k start) (x 0)) - (if (##fx< k end) - (begin - (##u8vector-set! buf x - (##fxxor (##u8vector-ref data k) - (##u8vector-ref mask (##fxmodulo x 4)))) - (lp (##fx+ k 1) (##fx+ x 1))) - (write-subu8vector buf 0 x port)))) - - (def (send-frame port fin opcode data start end) - (let* ((plen (##fx- end start)) - (fin (##fxarithmetic-shift fin 7)) - (b0 (##fxior fin opcode)) - (mask (##fxarithmetic-shift 1 7)) - (b1 (##fxior mask (if (##fx< plen 126) plen 126)))) - (random-bytes! mask-bytes) - (write-u8 b0 port) - (write-u8 b1 port) - (unless (##fx< plen 126) - (write-u16 plen port)) - (write-subu8vector mask-bytes 0 4 port) - (write-payload mask-bytes data start end port) - (force-output port))) - - (def (send port opcode data) - (let (end (u8vector-length data)) - (let lp ((start 0) (opcode opcode)) - (let (have (##fx- end start)) - (if (##fx< have 65536) - (send-frame port 1 opcode data start end) - (let (xend (##fx+ start 65535)) - (send-frame port 0 opcode data start xend) - (lp xend #x0))))))) - - (with ((websocket port) ws) - (try - (let lp () - (match (thread-receive) - (['text . data] - (send port #x1 data) - (lp)) - (['binary . data] - (send port #x2 data) - (lp)) - (['pong . data] - (send port #xA data) - (lp)) - (['close . how] - (when (fixnum? how) - (let (bytes (make-u8vector 2)) - (##u8vector-set! bytes 0 (##fxand (##fxarithmetic-shift how -8) #xff)) - (##u8vector-set! bytes 1 (##fxand how #xff)) - (send port #x8 bytes)))) - (bogus - (warnf "unexpected message ~a" bogus) - (lp)))) - (catch (e) - (errorf "unhandled exception: ~a" e) - (websocket-close ws 'abort) ; notify receivers - (with-catch void (cut close-port port))) ; unblock reader - (finally - (with-catch void (cut close-output-port port)))))) - -;; ------>8------>8------>8------>8------>8------>8------>8------>8------>8------ ;; -*- Gerbil -*- ;;;; Utilities for using websocket - +(import + :gerbil/gambit + :std/actor + :std/contract + :std/crypto/digest + :std/error + :std/logger + :std/misc/queue + :std/misc/timeout + :std/net/request + :std/net/websocket + :std/srfi/13 + :std/sugar + :std/text/base64 + :std/text/json + :std/text/utf8 + :clan/base) (defmessage !receive (msg)) (defmessage !send (msg)) @@ -525,17 +45,17 @@ And/or implement QUIC instead. encode-message: (encode-message websocket-encode-message-json)) ;; Start websocket client - (def client (connect-limiter open-websocket-client url)) + (def client (connect-limiter websocket-connect url)) (def controller (current-thread)) (def receiver (spawn receiver-loop)) (def (receive-message) - (let ((values bytes type) (websocket-recv client)) - (decode-message bytes type))) + (using (msg (WebSocket-recv client) :- message) + (decode-message msg.data msg.type))) (def (send-message message) - (let ((values bytes type) (encode-message message)) - (websocket-send client bytes type))) + (let ((values data type) (encode-message message)) + (WebSocket-send client (message data type)))) (def (receiver-loop) (try @@ -559,5 +79,4 @@ And/or implement QUIC instead. ,(@unexpected warnf) (shutdown)))) (finally - (websocket-close client hard: #t)))) -|# + (WebSocket-close client)))) diff --git a/otp.ss b/otp.ss index 804f0aa..f429aac 100644 --- a/otp.ss +++ b/otp.ss @@ -6,20 +6,23 @@ nix-env -f '' -iA oath-toolkit ;; Create your personal executable otp script with content such as below: #!/usr/bin/env gxi +(export #t) (import :clan/otp) (def main otp) -(set! otp-keys '(("myaccount@gmail.com" "abcd efgh ijkl mnop qrst uvwx yz12 3456") - ("myaccount@github.com" "0123456789ABCDEF"))) +(register-otp-keys + myaccount@gmail.com: "abcd efgh ijkl mnop qrst uvwx yz12 3456" + myaccount@github.com: "0123456789ABCDEF") |# (export #t) (import + (only-in :std/cli/getopt optional-argument) + (only-in :std/cli/multicall define-entry-point) (only-in :std/format printf) - (only-in :std/getopt optional-argument) + (only-in :std/misc/alist plist->alist) (only-in :std/misc/process run-process) - (only-in :std/srfi/1 second) - (only-in :clan/multicall define-entry-point)) + (only-in :std/srfi/1 second)) (def otp-keys (values [])) @@ -38,3 +41,10 @@ nix-env -f '' -iA oath-toolkit (help: "show otp" getopt: [(optional-argument 'user)]) (if user (show-otp user) (show-all-otps))) + +(def (plist->keys plist) + (map (match <> ([k . v] (list (as-string k) (as-string v)))) + (plist->alist plist))) + +(def (register-otp-keys . plist) + (set! otp-keys (append otp-keys (plist->keys plist)))) diff --git a/random.ss b/random.ss index c4d23ee..b664320 100644 --- a/random.ss +++ b/random.ss @@ -6,20 +6,20 @@ (import (only-in :gerbil/gambit random-source-randomize! default-random-source) (only-in :std/misc/list-builder with-list-builder) - (only-in :std/misc/number decrement!) - (only-in :std/misc/bytes nat-length-in-u8 u8vector->nat) + (only-in :std/misc/number decrement! uint-length-in-u8) + (only-in :std/misc/bytes u8vector->uint) (only-in :std/crypto/etc random-bytes) (only-in ./base nest λ)) ;; cryptographically random integers -(def (random-nat end) - ;; Instead of skew or retries from (nat-length-in-u8 (1- end)) bytes, +(def (random-uint end) + ;; Instead of skew or retries from (uint-length-in-u8 (1- end)) bytes, ;; take 8 more bytes and consider the skew negligible. - (def n (u8vector->nat (random-bytes (+ (nat-length-in-u8 end) 7)))) + (def n (u8vector->uint (random-bytes (+ (uint-length-in-u8 end) 7)))) (modulo n end)) (def (random-char bag) - (string-ref bag (random-nat (string-length bag)))) + (string-ref bag (random-uint (string-length bag)))) (def (generate-list n generate-element) (if (zero? n) '() (cons (generate-element) (generate-list (- n 1) generate-element)))) (def (n-random-chars n bag) @@ -43,7 +43,7 @@ (with-list-builder (collect _)) (let loop ((end (if n (min n len) len)))) (when (< 0 end)) - (let* ((i (random-nat end)) + (let* ((i (random-uint end)) (max (- end 1))) (collect (vector-ref vec i)) (vector-set! vec i (vector-ref vec max)) @@ -55,4 +55,4 @@ ((zero? n) sum) (else (decrement! n) - (loop (+ sum 1 (random-nat sides))))))) + (loop (+ sum 1 (random-uint sides))))))) diff --git a/roman.ss b/roman.ss index 202141f..c922c04 100644 --- a/roman.ss +++ b/roman.ss @@ -3,7 +3,8 @@ (export #t) (import - :gerbil/gambit) + :gerbil/gambit + :std/error) ;; Convert one digit to a roman numeral, given strings for one unit, five units and ten units. (def (roman-numeral<-digit digit (i "I") (v "V") (x "X")) @@ -22,8 +23,8 @@ (def (roman-numeral<-integer n) ;; NB: Only works for integer from 1 to 3999. - (when (or (not (exact-integer? n)) (< n 1) (> n 3999)) - (error "I cannot convert ~s to a roman numeral" n)) + (check-argument (and (exact-integer? n) (<= 1 n 3999)) + "integer convertible to roman numeral" n) (let* ((units (modulo n 10)) (n/10 (/ (- n units) 10)) (tens (modulo n/10 10)) diff --git a/scribble.ss b/scribble.ss new file mode 100644 index 0000000..13755fc --- /dev/null +++ b/scribble.ss @@ -0,0 +1,323 @@ +;; Scribble: Racket-like scribble reader extension for Gerbil Scheme -*- Scheme -*- +;; WIP -- gotta figure out how to integrate into Gambit / Gerbil 's reader API, +;; which differs from CL. Also gotta figure out the column situation. + +;; See Racket documentation: http://docs.racket-lang.org/scribble/reader.html +;; And racket source code: pkgs/at-exp-lib/scribble/reader.rkt + +(export #t) +(import + :std/io + (only-in :std/srfi/13 string-index) + (only-in :std/text/char-set def-codepoint codepoint-ascii-alphanumeric? + char-strict-whitespace?) + (only-in :std/misc/number increment! decrement!) + ./base) + +(def-codepoint (ascii-punctuation? c) + (and (<= 33 c 126) (not (codepoint-ascii-alphanumeric? c)))) + +#| +;; Parse an @ expression. +(def (parse-at-syntax input) + (using (input :- BufferedInput) + (def o (open-output-string)) ; buffered output of "current stuff" + (def i (make-instance 'buffered-input :stream input)) + (def cmdonly #f) + (def col 0) + (def line '()) + (def lines '()) + (def mrof '()) ; current form (reversed) + (def (expect pred) ((parse-maybe-one-of pred) input)) + (def (expect= c) (expect (cut eqv? <> c))) + (def (expect-in string) (expect (cut string-index string <>))) + ;; functions starting with ? process input after matching what is described in the name, + ;; e.g. ?at processes input after an at-sign @. + ;; those ending with ! issue output. + (def (?at) ; what to do after a @ + (cond + ((expect char-strict-whitespace?) + (raise-parse-error parse-at-syntax "Unexpected whitespace after @" input)) + ((expect= #\;) + (?at-comment)) + (else + (?punctuation)))) + (def (?at-comment) ; what to do after @; + (cond + ((expect= #\{) (?brace-text)) + (else (read-line input))) + (parse-and-skip-any-whitespace input)) + (def (?punctuation) + (def char (expect-in "'`,")) + (case char + ((#\') (?quote)) + ((#\`) (?backquote)) + ((#\,) (cond + ((expect= #\@) (?comma-at)) + ((expect= #\.) (?comma-dot)) + (else (?comma)))) + (else (?cmd)))) + (def (?quote) + (kwote (?punctuation))) + (def (?backquote) + (call-with-quasiquote-reader ?punctuation)) + (def (?comma-at) + (call-with-unquote-splicing-reader ?punctuation)) + (def (?comma-dot) + (call-with-unquote-nsplicing-reader ?punctuation)) + (def (?comma) + (call-with-unquote-reader ?punctuation)) + (def (?cmd) + (def char (expect-in "|[{")) + (case char + ((#\|) (?maybe-alttext ?at-pipe)) + ((#\[ #\{) (?datatext char)) + (else (?cmd1)))) + (def (?maybe-alttext cont) + (input.unread-char #\|) + (def k (?newkey)) + (cond + (k + (set! cmdonly #f) + (?brace-alttext k)) + (else + (cont)))) + (def (?at-pipe) + (expect= #\|) ;; re-read and drop the #\| + (def r (expect-to-char #\| i)) + (define-values (s n) (read-from-string r)) + (unless (eof-object? (ignore-errors (read-from-string r #f #!eof :start n))) + (raise-parse-error parse-at-syntax "Unexpected characters in ~S after position ~D" r n)) + (set! cmdonly #t) + (form! s) + (?end)) + (def (?cmd1) + (set! cmdonly #t) + (form! (parse-whitespace input)) + (?cmd2)) + (def (?cmd2) + (def char (expect-in "[{|")) + (if char + (?datatext char) + (?end))) + (def (form! x) + (push! x mrof)) + (def (?datatext char) + (case char + (#\[ (?square-bracket-data)) + ((#\{ #\|) (unread-char* i char) (?brace-text0)))) + (def (?square-bracket-data) + (set! cmdonly #f) + (for-each form! (read-delimited-list #\] input #t)) + (?brace-text0)) + (def (?brace-text0) + (cond + ((expect= #\{) + (set! cmdonly #f) + (?brace-text)) + ((expect= #\|) + (?maybe-alttext ?end)) + (else (?end)))) + (def (?newkey) + (expect= #\|) + (let loop ((r '())) + (def c (input.read-char)) + (cond + ((and (char-ascii-punctuation? c) (not (string-index "@|{" c))) + (push! c r) (loop)) + ((eqv? c #\{) + (list->string (reverse r))) + (else + (input.put-back (reverse r)) + #f)))) + (def (char! c) + (write-char c o)) + (def (flush!) + (def s (get-output-string o)) + (when (plus? (length s)) + (push! s line))) + (def (eol! eol) + (def s0 (get-output-string o)) + (def s (if eol (trim-ending-spaces s0) s0)) + (when (plus? (length s)) + (push! s line)) + (push! (cons col (reverse line)) lines) + (when eol + (set! col (skip-whitespace-return-column i 0)) + (set! line '())) + #t) + (def (?brace-text) + (def brace-level 1) + (set! col (stream-line-column-harder input)) + (set! line '()) + (let loop () + (def c (input.read-char)) + (case c + ((#\return) + (expect= #\newline) + (eol! #t) + (loop)) + ((#\newline) + (eol! #t) + (loop)) + ((#\{) + (increment! brace-level) + (char! c) + (loop)) + ((#\@) + (?inside-at) + (loop)) + ((#\}) + (decrement! brace-level) + (cond + ((zero? brace-level) + (eol! #f) + (flush-text!) + (?end)) + (else + (char! c) + (loop)))) + (else + (char! c) + (loop))))) + (def (?inside-at) + (def c (expect-in ";\"|")) + (case c + ((#\;) + (cond + ((expect= #\{) + (let ((m mrof) (l line) (ls lines) (c col) (co cmdonly) (oo o)) + (set! o (open-output-string)) + (?brace-text) + (set! mrof m line l lines ls col c cmdonly co o oo))) + (else + (read-line input) + (skip-whitespace-return-column input)))) + ((#\") + (unread-char* i #\") + (flush-buffer i) + (write-string (read-preserving-whitespace input #t #f #f) o)) + ((#\|) + (flush!) + (let ((r (read-to-char #\| i))) + (with-input-from-string (s r) + (loop :for x = (read-preserving-whitespace s #f s #f) + :until (eq x s) :do (push! x line))))) + (else + (flush!) + (flush-buffer i) + (push! (parse-at-syntax input) line)))) + (def (flush-text!) + (def mincol (loop :for (col . strings) :in lines + :when strings :minimize col)) + (def text (loop :for (col . strings) :in (reverse lines) + :for first = #t :then #f + :append + `(,@(when (and strings (> col mincol) (not first)) + (list (n-spaces (- col mincol)))) + ,@strings ,*lf*))) + (when (eq *lf* (first text)) + (pop! text)) + (def e (every (lambda (x) (eq x *lf*)) text)) + (def r (reverse text)) + (unless e + (loop :repeat 2 :when (eq *lf* (first r)) :do (pop r))) + (set! mrof (append r mrof)) + #t) + (def (?brace-alttext key) + (def brace-level 1) + (def rkey (mirror-string key)) + (input.flush-buffer) + (set! col (stream-line-column-harder input)) + (set! line '()) + (let/cc return + (while #t + (def c (input.read-char)) + (case c + ((#\return) + (expect= #\newline) + (eol! #t)) + ((#\newline) + (eol! #t)) + ((#\|) + (if (not (expect-string i key)) + (char! #\|) + (let ((c (expect-in "@{"))) + (case c + ((#\{) + (increment! brace-level) + (char! #\|) + (string-for-each char! key) + (char! c)) + ((#\@) + (?inside-at)) + (else + (input.put-back (string->list key)) + (char! #\|)))))) + ((#\}) + (cond + ((not (expect-string i rkey)) + (char! #\})) + ((expect= #\|) + (decrement! brace-level) + (cond + ((zero? brace-level) + (eol! #f) + (flush-text!) + (return (?end))) + (else + (char! #\}) + (string-for-each char! rkey) + (char! #\|)))) + (else + (unread-string i rkey) + (char! #\})))) + (else + (char! c)))))) + (def (?end) + (flush-buffer i) + (if (and cmdonly (length=n? mrof 1)) + (car mrof) + (reverse mrof))) + (?at))) ;; a @ character was just read by who called this function, so start parsing at ?at + +(defun read-at-syntax (stream &optional char) + (declare (ignore char)) + (parse-at-syntax stream)) +(defun forbidden-pipe-macro (stream char) + ;; if we allow pipes, then @foo|{bar}| gets read as @ followed by escaped symbol |foo{bar}| + ;; maybe we could make | a terminating macro and otherwise keep its meaning? + (declare (ignore stream char)) + (simple-parse-error "| not allowed when at syntax enabled")) + +(defun do-enable-scribble-at-syntax (&key (table *readtable*) scribe skribe) + (enable-quasiquote :table table) + (flet ((s (char fun) (set-macro-character char fun #f table))) + (s #\[ read-paren-list) + (s #\] unbalanced-paren) + (s #\{ read-paren-list) + (s #\} unbalanced-paren) + (s #\@ read-at-syntax) + (s #\| forbidden-pipe-macro) + (when (or scribe skribe) ;; backward compatibility with former scribble? + (do-enable-scribble-syntax table))) + #t) + +(defvar *scribble-at-readtable* #f) +(defun enable-scribble-at-syntax (&key (table *readtable*) (scribe #f)) + (set! *scribble-at-readtable* (push-readtable table)) + (do-enable-scribble-at-syntax :table *scribble-at-readtable* :scribe scribe) + *scribble-at-readtable*) +(defun disable-scribble-at-syntax () + (pop-readtable)) +(def (reenable-scribble-at-syntax scribe: (scribe #f)) + (if (readtable? *scribble-at-readtable*) + (enable-scribble-at-syntax :scribe scribe) + (push-readtable *scribble-at-readtable*)) + *scribble-at-readtable*) + +(def (parse-at-string s) + (def i (open-buffered-string-reader s)) + (parameterize ((*readtable* *scribble-at-readtable*)) + (parse-at-syntax i))) +|# diff --git a/scripts/make-docker-image.ss b/scripts/make-docker-image.ss index 66a397b..f7deffb 100755 --- a/scripts/make-docker-image.ss +++ b/scripts/make-docker-image.ss @@ -7,9 +7,11 @@ (export #t) (import + (only-in :std/cli/getopt argument optional-argument rest-arguments) + (only-in :std/cli/multicall define-multicall-main define-entry-point set-default-entry-point!) (only-in :std/crypto/digest make-digest digest::sha256 digest-update! digest-final!) + #;(only-in :std/error dump-stack-trace?) ;; Only in v0.19 (only-in :std/format format fprintf) - (only-in :std/getopt argument optional-argument rest-arguments) (only-in :std/iter for) (only-in :std/misc/list length=n? when/list) (only-in :std/misc/path subpath path-simplify path-parent) @@ -22,11 +24,9 @@ (only-in :std/srfi/1 lset-difference any first second third remove append-map) (only-in :std/srfi/13 string-suffix?) (only-in :std/text/hex hex-encode) - (only-in :clan/exit backtrace-on-abort?) (only-in :clan/files clobber-file) (only-in :clan/filesystem path-is-directory?) (only-in :clan/memo define-memo-function) - (only-in :clan/multicall define-multicall-main define-entry-point set-default-entry-point!) (only-in :clan/path-config cache-path set-path-config-root! application-name) (only-in :clan/ports output-contents) (only-in :clan/shell escape-shell-token) @@ -166,7 +166,7 @@ ;; docker image prune -a ;; (def (docker-commit-image from to mounts: (mounts []) changes: (changes []) . commands) - (def build-name (string-substitute #\- #\/ to)) + (def build-name (string-substitute to #\- #\/)) (def build-script (subpath docker-directory (string-append build-name ".sh"))) (unless (docker-image-id to) (create-directory* docker-directory) @@ -411,7 +411,7 @@ (make-images nixpkgs)) (set-default-entry-point! 'all) -(backtrace-on-abort? #f) +#;(dump-stack-trace? #f) ;; Only in v0.19 (define-multicall-main) #| diff --git a/scripts/random-run.ss b/scripts/random-run.ss index 4fd1095..1e9c510 100644 --- a/scripts/random-run.ss +++ b/scripts/random-run.ss @@ -4,10 +4,12 @@ (export #t) (import - :std/format :std/getopt :std/logger :std/iter + :std/cli/getopt + :std/cli/multicall + :std/format :std/logger :std/iter :std/misc/list :std/misc/process :std/srfi/1 :std/sugar :clan/base :clan/error :clan/list - :clan/filesystem :clan/multicall :clan/random) + :clan/filesystem :clan/random) (define-entry-point (random-run . arguments) (help: "Run a command with arguments in random order" diff --git a/scripts/update-gerbil-nix-recipe.ss b/scripts/update-gerbil-nix-recipe.ss index 166cf41..94dc7cb 100755 --- a/scripts/update-gerbil-nix-recipe.ss +++ b/scripts/update-gerbil-nix-recipe.ss @@ -8,8 +8,9 @@ (import :gerbil/gambit + :std/cli/getopt + :std/cli/multicall :std/format - :std/getopt :std/misc/list :std/misc/path :std/misc/ports @@ -20,7 +21,6 @@ :std/text/basic-parsers :clan/base :clan/files - :clan/multicall :clan/timestamp) ;; Initialize paths from the environment @@ -148,7 +148,7 @@ (call-with-input-process [path: "nix-prefetch-git" ;; TODO: enable this flag & update nix recipe next time gambit breaks gerbil - arguments: ["--fetch-submodules" + arguments: ["--fetch-submodules" "--no-deepClone" ;;"--sparse-checkout" "--url" (string-append "file://" source-dir) "--rev" latest-commit-hash] show-console: #f stderr-redirection: #f] (λ (port) diff --git a/source.ss b/source.ss index e073baa..f0de201 100644 --- a/source.ss +++ b/source.ss @@ -28,7 +28,7 @@ (import (only-in :std/srfi/141 floor/) (only-in :std/misc/path subpath) - (only-in ./base ignore-errors) + (only-in :std/sugar ignore-errors) (only-in ./path-config source-path)) (export #t) diff --git a/string.ss b/string.ss index c1da685..4f5fc9d 100644 --- a/string.ss +++ b/string.ss @@ -6,28 +6,6 @@ :std/iter :std/misc/number :std/srfi/13 :std/text/char-set ./list) -;; TODO: write a string-substitute function in the style of http://clhs.lisp.se/Body/f_sbs_s.htm -;; and/or of SRFI 13 (that will be contributed to std/misc/string); -;; and/or port and contribute to Gerbil an existing SRFI that has such a function already if any. -;; String <- Char Char String from-end: ? Bool start: ? (OrFalse Fixnum) end: ? (OrFalse Fixnum) count: (OrFalse Fixnum) -(def (string-substitute new-char old-char string - from-end: (from-end #f) - start: (start #f) - end: (end #f) - count: (count #f)) - (let* ((l (string-length string)) - (new-string (make-string l)) - (k 0)) - (for ((i (if from-end (in-iota l (1- l) -1) (in-iota l)))) - (let ((char (string-ref string i))) - (string-set! new-string i (if (and (eqv? char old-char) - (or (not start) (<= start i)) - (or (not end) (< i end)) - (or (not count) (< k count))) - (begin (increment! k) new-char) - char)))) - new-string)) - ;; Given a string, return it with any beginning or ending whitespace trimmed off ;; String <- String (def (string-trim-spaces string) diff --git a/t/base-test.ss b/t/base-test.ss index c16b8c4..1e165fc 100644 --- a/t/base-test.ss +++ b/t/base-test.ss @@ -1,6 +1,7 @@ (export base-test) (import + :std/sugar :std/test ../base) diff --git a/t/exception-test.ss b/t/exception-test.ss index cddc3ab..a0b4b6e 100644 --- a/t/exception-test.ss +++ b/t/exception-test.ss @@ -35,8 +35,8 @@ exn-in-ctx backtrace) (check exn-in-ctx ? - (lambda (x) (string-prefix? "*** ERROR IN clan/t/exception-test#inside3, \"t/exception-test.ss\"@14.26 -- \n*** ERROR IN ? [Error]: inside4\n--- irritants: \"arg\" \n--- continuation backtrace:\n" x))) + (lambda (x) (string-prefix? "*** ERROR IN clan/t/exception-test#inside3, \"t/exception-test.ss\"@14.26-14.44 -- \n*** ERROR IN ? [Error]: inside4\n--- irritants: \"arg\" \n--- continuation backtrace:\n" x))) (check backtrace ? - (lambda (x) (string-prefix? "0 clan/t/exception-test#" x)))) + (lambda (x) (string-prefix? "[0] clan/t/exception-test#" x)))) )) diff --git a/t/json-test.ss b/t/json-test.ss index ba18b9b..5f9457c 100644 --- a/t/json-test.ss +++ b/t/json-test.ss @@ -18,14 +18,14 @@ (def json-test (test-suite "test suite for clan/json" (test-case "trivial-json<-struct, trivial-struct<-json" - (defrule (t struct json) + (defrule (t struct alist) (begin - (checkf equal-struct? (json-rpc-error<-json json) struct) - (check-equal? {:json struct} json))) + (checkf equal-struct? (json-rpc-error<-json (list->hash-table alist)) struct) + (check-equal? {:json struct} (walist alist)))) (parameterize ((json-symbolic-keys #t)) (check equal-struct? (json-rpc-error -1 "foo" [42]) (json-rpc-error -1 "foo" [42])) - (t (json-rpc-error -1 "foo" [42]) (hash (code -1) (message "foo") (data [42]))) - (t (json-rpc-error -100 "bar" (void)) (hash (code -100) (message "bar") (data (void)))) + (t (json-rpc-error -1 "foo" [42]) '((code . -1) (message . "foo") (data . (42)))) + (t (json-rpc-error -100 "bar" (void)) '((code . -100) (message . "bar") (data . #!void))) (check equal-struct? (json-rpc-error<-json (hash (code -2) (message "x"))) (json-rpc-error -2 "x" (void))))) diff --git a/t/random-test.ss b/t/random-test.ss index 2f36d8c..faa1e80 100644 --- a/t/random-test.ss +++ b/t/random-test.ss @@ -6,5 +6,5 @@ (def random-test (test-suite "test suite for clan/random" - (test-case "test random-nat" - (check-equal? (<= 0 (random-nat 100) 99) #t)))) + (test-case "test random-uint" + (check-equal? (<= 0 (random-uint 100) 99) #t)))) diff --git a/t/string-test.ss b/t/string-test.ss index e5311d6..240bd1c 100644 --- a/t/string-test.ss +++ b/t/string-test.ss @@ -8,10 +8,6 @@ (def string-test (test-suite "test suite for clan/string" - (test-case "test string-substitute" - (check-equal? (string-substitute #\o #\a "banana") "bonono") - (check-equal? (string-substitute #\b #\n "bonono" from-end: #t count: 1) "bonobo") - (check-equal? (string-substitute #\b #\n "bonono" start: 4) "bonobo")) (test-case "test string-trim-spaces" (check-equal? (string-trim-spaces "banana") "banana") (check-equal? (string-trim-spaces " spaces and tabs ") "spaces and tabs")) diff --git a/t/vector-test.ss b/t/vector-test.ss deleted file mode 100644 index 531cbf1..0000000 --- a/t/vector-test.ss +++ /dev/null @@ -1,27 +0,0 @@ -(export #t) - -(import - :gerbil/gambit - :std/misc/number - :std/sugar - :std/error :std/text/hex :std/test :std/srfi/1 - ../vector) - -(def vector-test - (test-suite "test suite for clan/vector" - (test-case "vector-scan-index" - (check-equal? (vector-scan-index positive? #(-10 -6 -2 -1 0 3 8 19)) 5) - (check-equal? (vector-scan-index positive? #(-20 -16 -12 -11 -10 -9 -8 -3)) #f) - (check-equal? (vector-scan-index positive? #(3 8 19 23 42 57 83)) 0)) - (test-case "vector-scan-index-right" - (check-equal? (vector-scan-index-right negative? #(-10 -6 -2 -1 0 3 8 19)) 3) - (check-equal? (vector-scan-index-right negative? #(-20 -16 -12 -11 -10 -9 -8 -3)) 7) - (check-equal? (vector-scan-index-right negative? #(3 8 19 23 42 57 83)) #f)) - (test-case "vector-least-index" - (check-equal? (vector-least-index positive? #(-10 -6 -2 -1 0 3 8 19)) 5) - (check-equal? (vector-least-index positive? #(-20 -16 -12 -11 -10 -9 -8 -3)) 8) - (check-equal? (vector-least-index positive? #(3 8 19 23 42 57 83)) 0)) - (test-case "vector-most-index" - (check-equal? (vector-most-index negative? #(-10 -6 -2 -1 0 3 8 19)) 4) - (check-equal? (vector-most-index negative? #(-20 -16 -12 -11 -10 -9 -8 -3)) 8) - (check-equal? (vector-most-index negative? #(3 8 19 23 42 57 83)) 0)))) diff --git a/testing.ss b/testing.ss index 84936c7..ced42f4 100644 --- a/testing.ss +++ b/testing.ss @@ -8,24 +8,26 @@ (export #t) (import - :gerbil/gambit - :gerbil/expander + (only-in :gerbil/gambit random-source-randomize! default-random-source + random-source-state-ref random-source-state-set!) + (only-in :gerbil/expander import-module) (only-in :gerbil/runtime/init add-load-path) - :std/error - :std/format - :std/getopt - :std/iter - :std/misc/bytes - :std/misc/path - :std/misc/process - :std/misc/repr - :std/sort - :std/source - :std/stxutil - :std/sugar - :std/test - :std/text/hex - ./base ./exit ./filesystem ./git-fu ./io ./multicall + (only-in :std/cli/getopt rest-arguments) + (only-in :std/cli/multicall define-entry-point set-default-entry-point! + current-program define-multicall-main) + (only-in :std/cli/print-exit silent-exit) + (only-in :std/error Error? Error-message) + (only-in :std/format printf) + (only-in :std/iter for/collect in-range) + (only-in :std/misc/path subpath path-maybe-normalize path-enough path-simplify) + (only-in :std/misc/process) + (only-in :std/misc/repr repr) + (only-in :std/sort sort) + (only-in :std/source this-source-file) + (only-in :std/sugar with-id) + (only-in :std/test run-tests! test-report-summary! test-result) + (only-in :std/text/hex hex-decode hex-encode) + ./base ./filesystem ./git-fu ./io ./path-config ./ports ./versioning) ;; Given a directory name (with no trailing /), is it a test directory named "t"? @@ -121,12 +123,12 @@ (def (0x<-random-source (rs default-random-source)) (def (bytes<-6u32 l) - (call-with-output-u8vector (lambda (port) (for-each (lambda (x) (write-nat-u8vector x 4 port)) l)))) + (call-with-output-u8vector (lambda (port) (for-each (lambda (x) (write-uint-u8vector x 4 port)) l)))) (!> rs random-source-state-ref vector->list bytes<-6u32 hex-encode)) (def (random-source<-0x! 0x (rs default-random-source)) (def (6u32<-bytes b) (call-with-input-u8vector - b (lambda (port) (for/collect (_ (in-range 6)) (read-nat-u8vector 4 port))))) + b (lambda (port) (for/collect (_ (in-range 6)) (read-uint-u8vector 4 port))))) (!> 0x hex-decode 6u32<-bytes list->vector (cut random-source-state-set! rs <>))) ;; Call this function at the beginning of any test involving randomness. diff --git a/versioning.ss b/versioning.ss index 860b906..8c1e372 100644 --- a/versioning.ss +++ b/versioning.ss @@ -11,6 +11,7 @@ (import :gerbil/gambit :std/format :std/iter :std/misc/list :std/misc/ports + :std/cli/getopt :std/cli/multicall :std/misc/process :std/misc/string :std/pregexp :std/srfi/1) (extern namespace: #f gerbil-greeting) @@ -57,3 +58,10 @@ ;; TODO: use FFI for that -- except it differs on Linux, BSD (mac?), Windows. (def machine-name (let (d (delay (##os-host-name))) (cut force d))) + +;; TODO: add a flag for short? +(define-entry-point (version complete: (complete #f) layer: (layer #f)) + (help: "Print software version" + getopt: [(flag 'complete "-C" "--complete" help: "also show versions of previous layers") + (option 'layer "-L" "--layer" help: "show versions for said layer")]) + (show-version complete: complete layer: layer)) diff --git a/watch.ss b/watch.ss index 629a28d..d79d624 100644 --- a/watch.ss +++ b/watch.ss @@ -25,9 +25,25 @@ (import :gerbil/gambit - :std/actor :std/format :std/logger :std/misc/list :std/misc/ports :std/misc/process - :std/pregexp :std/srfi/13 :std/sugar :std/text/basic-parsers - ./base ./timestamp ./ffi ./files ./json ./list ./logger ./path-config) + :std/actor + :std/format + :std/logger + :std/misc/list + :std/misc/ports + :std/misc/process + (only-in :std/pregexp pregexp-match) + (only-in :std/srfi/13 string-every) + (only-in :std/sugar while try finally hash ignore-errors) + (only-in :std/text/basic-parsers parse-port + parse-and-skip-any-whitespace parse-natural parse-separated parse-eof) + ./base + ./timestamp + ./ffi + ./files + ./json + ./list + ./logger + ./path-config) ;; Class Daemon-Status-Register (defclass daemon-status-register ()) @@ -124,14 +140,8 @@ (loop)))) (def (read-integer-list port) - (nest - (and port) - (with-list-builder (c)) - (let loop () - (parse-and-skip-any-whitespace port)) - (unless (eof-object? (peek-char port)) - (c (parse-natural port)) - (loop)))) + (let (parser (parse-separated parse-natural parse-and-skip-any-whitespace parse-eof)) + (and port (parse-port parser port)))) ;; TODO: make it portable beyond Linux. At least make it error out outside Linux. ;; TODO: is ignore-errors working? Should we use it?