;;;; swank-goo.goo --- Swank server for GOO ;;; ;;; Copyright (C) 2005 Helmut Eller ;;; ;;; This file is licensed under the terms of the GNU General Public ;;; License as distributed with Emacs (press C-h C-c to view it). ;;;; Installation ;; ;; 1. Add something like this to your .emacs: ;; ;; (setq slime-lisp-implementations ;; '((goo ("g2c") :init goo-slime-init))) ;; ;; (defun goo-slime-init (file _) ;; (format "%S\n%S\n" ;; `(set goo/system:*module-search-path* ;; (cat '(".../slime/contrib/") ;; goo/system:*module-search-path*)) ;; `(swank-goo:start-swank ,file))) ;; ;; 2. Start everything with M-- M-x slime goo ;; ;;;; Code (use goo) (use goo/boot) (use goo/x) (use goo/io/port) (use goo/io/write) (use goo/eval) (use goo/system) (use goo/conditions) (use goo/fun) (use goo/loc) (use goo/chr) (use eval/module) (use eval/ast) (use eval/g2c) ;;;; server setup (df create-server (port-number) (setup-server port-number announce-port)) (df start-swank (port-file) (setup-server 0 (fun (s) (write-port-file (%local-port s) port-file)))) (df setup-server (port-number announce) (let ((s (create-socket port-number))) (fin (seq (announce s) (let ((c (accept s))) ;;(post "connection: %s" c) (fin (serve-requests c) (%close (@fd c))))) (post "closing socket: %s" s) (%close s)))) (df announce-port (socket) (post "Listening on port: %d\n" (%local-port socket))) (df write-port-file (port-number filename) (with-port (file (open <file-out-port> filename)) (msg file "%d\n" port-number))) (dc <slime-toplevel> (<restart>)) (dc <connection> (<any>)) (dp @socket (<connection> => <port>)) (dp @in (<connection> => <in-port>)) (dp @out (<connection> => <out-port>)) (dv emacs-connection|(t? <connection>) #f) (df serve-requests (socket) (dlet ((emacs-connection (new <connection> @socket socket @out (new <slime-out-port> @socket socket) @in (new <slime-in-port> @socket socket)))) (dlet ((out (@out emacs-connection)) (in (@in emacs-connection))) (while #t (simple-restart <slime-toplevel> "SLIME top-level" (fun () (process-next-event socket))))))) (d. <nil> (t= 'nil)) (d. t #t) (d. cons pair) (dv tag-counter|<int> 0) (df process-next-event (port) (dispatch-event (decode-message port) port)) (df dispatch-event (event port) ;; (post "%=\n" event) (match event ((:emacs-rex ,form ,package ,_thread-id ,id) (eval-for-emacs form package port id)) ((:read-string ,_) (def tag (incf tag-counter)) (encode-message `(:read-string ,_ ,tag) port) (rep loop () (match (decode-message port) ((:emacs-return-string ,_ ,rtag ,str) (assert (= tag rtag) "Unexpected reply tag: %d" rtag) str) ((,@evt) (try-recover (fun () (dispatch-event evt port)) (fun () (encode-message `(:read-aborted ,_ ,tag) port))) (loop))))) ((:emacs-return-string ,_ ,rtag ,str) (error "Unexpected event: %=" event)) ((,@_) (encode-message event port)))) (dc <eval-context> (<any>)) (dp @module (<eval-context> => <module>)) (dp @id (<eval-context> => <int>)) (dp @port (<eval-context> => <port>)) (dp @prev (<eval-context> => (t? <eval-context>))) ;; should be ddv (dv eval-context|(t? <eval-context>) #f) (df buffer-module () (@module eval-context)) (df eval-for-emacs (form|<lst> package|(t+ <str> <nil>) port id|<int>) (try-recover (fun () (try <condition> debugger-hook (dlet ((eval-context (new <eval-context> @module (find-buffer-module package) @id id @port port @prev eval-context))) (def result (eval (frob-form-for-eval form) 'swank-goo)) (force-out out) (dispatch-event `(:return (:ok ,result) ,id) port)))) (fun () (dispatch-event `(:return (:abort) ,id) port)))) (dm find-buffer-module (name|<str> => <module>) (or (elt-or (all-modules) (as-sym name) #f) (find-buffer-module 'nil))) (dm find-buffer-module (name|<nil> => <module>) default-module) (dv default-module|<module> (runtime-module 'goo/user)) (d. slimefuns (fab <tab> 100)) (ds defslimefun (,name ,args ,@body) `(set (elt slimefuns ',name) (df ,(cat-sym 'swank@ name) ,args ,@body))) (df slimefun (name) (or (elt-or slimefuns name #f) (error "Undefined slimefun: %=" name))) ;; rewrite (swank:foo ...) to ((slimefun 'foo) ...) (df frob-form-for-eval (form) (match form ((,op ,@args) (match (map as-sym (split (sym-name op) #\:)) ((swank ,name) `((slimefun ',name) ,@args)))))) ;;;; debugger (dc <sldb-context> (<any>)) (dp @level (<sldb-context> => <int>)) (dp @top-frame (<sldb-context> => <lst>)) (dp @restarts (<sldb-context> => <lst>)) (dp @condition (<sldb-context> => <condition>)) (dp @eval-context (<sldb-context> => (t? <eval-context>))) (dv sldb-context|(t? <sldb-context>) #f) (df debugger-hook (c|<condition> resume) (let ((tf (find-top-frame 'debugger-hook 2)) (rs (compute-restarts c)) (l (if sldb-context (1+ (@level sldb-context)) 1))) (cond ((> l 10) (emergency-abort c)) (#t (dlet ((sldb-context (new <sldb-context> @level l @top-frame tf @restarts rs @condition c @eval-context eval-context))) (let ((bt (compute-backtrace tf 0 10))) (force-out out) (dispatch-event `(:debug 0 ,l ,@(debugger-info c rs bt eval-context)) (@port eval-context)) (sldb-loop l (@port eval-context)))))))) (df emergency-abort (c) (post "Maximum debug level reached aborting...\n") (post "%s\n" (describe-condition c)) (do-stack-frames (fun (f args) (msg out " %= %=\n" f args))) (invoke-handler-interactively (find-restart <slime-toplevel>) in out)) (df sldb-loop (level port) (fin (while #t (dispatch-event `(:debug-activate 0 ,level) port) (simple-restart <restart> (msg-to-str "Return to SLDB level %s" level) (fun () (process-next-event port)))) (dispatch-event `(:debug-return 0 ,level nil) port))) (defslimefun backtrace (start|<int> end|(t+ <int> <nil>)) (backtrace-for-emacs (compute-backtrace (@top-frame sldb-context) start (if (isa? end <int>) end #f)))) (defslimefun throw-to-toplevel () (invoke-handler-interactively (find-restart <slime-toplevel>) in out)) (defslimefun invoke-nth-restart-for-emacs (sldb-level|<int> n|<int>) (when (= (@level sldb-context) sldb-level) (invoke-handler-interactively (elt (@restarts sldb-context) n) in out))) (defslimefun debugger-info-for-emacs (start end) (debugger-info (@condition sldb-context) (@restarts sldb-context) (compute-backtrace (@top-frame sldb-context) start (if (isa? end <int>) end #f)))) (defslimefun frame-locals-and-catch-tags (frame-idx) (def frame (nth-frame frame-idx)) (list (map-keyed (fun (i name) (lst ':name (sym-name name) ':id 0 ':value (safe-write-to-string (frame-var-value frame i)))) (frame-var-names frame)) '())) (defslimefun inspect-frame-var (frame-idx var-idx) (reset-inspector) (inspect-object (frame-var-value (nth-frame frame-idx) var-idx))) (defslimefun inspect-current-condition () (reset-inspector) (inspect-object (@condition sldb-context))) (defslimefun frame-source-location (frame-idx) (match (nth-frame frame-idx) ((,f ,@_) (or (emacs-src-loc f) `(:error ,(msg-to-str "No src-loc available for: %s" f)))))) (defslimefun eval-string-in-frame (string frame-idx) (def frame (nth-frame frame-idx)) (let ((names (frame-var-names frame)) (values (frame-var-values frame))) (write-to-string (app (eval `(fun ,names ,(read-from-string string)) (module-name (buffer-module))) values)))) (df debugger-info (condition restarts backtrace eval-context) (lst `(,(try-or (fun () (describe-condition condition)) "<...>") ,(cat " [class: " (class-name-str condition) "]") ()) (restarts-for-emacs restarts) (backtrace-for-emacs backtrace) (pending-continuations eval-context))) (df backtrace-for-emacs (backtrace) (map (fun (f) (match f ((,idx (,f ,@args)) (lst idx (cat (if (fun-name f) (sym-name (fun-name f)) (safe-write-to-string f)) (safe-write-to-string args)))))) backtrace)) (df restarts-for-emacs (restarts) (map (fun (x) `(,(sym-name (class-name (%handler-condition-type x))) ,(describe-restart x))) restarts)) (df describe-restart (restart) (describe-handler (%handler-info restart) (%handler-condition-type restart))) (df compute-restarts (condition) (packing (%do-handlers-of-type <restart> (fun (c) (pack c))))) (df find-restart (type) (esc ret (%do-handlers-of-type type ret) #f)) (df pending-continuations (context|(t? <eval-context>)) (if context (pair (@id context) (pending-continuations (@prev context))) '())) (df find-top-frame (fname|<sym> offset|<int>) (esc ret (let ((top-seen? #f)) (do-stack-frames (fun (f args) (cond (top-seen? (cond ((== offset 0) (ret (pair f args))) (#t (decf offset)))) ((== (fun-name f) fname) (set top-seen? #t)))))))) (df compute-backtrace (top-frame start|<int> end) (packing (esc break (do-user-frames (fun (idx f args) (when (and end (<= end idx)) (break #f)) (when (<= start idx) (pack (lst idx (pair f args))))) top-frame)))) (df nth-frame (n|<int>) (esc ret (do-user-frames (fun (idx f args) (when (= idx n) (ret (pair f args)))) (@top-frame sldb-context)))) (df frame-var-value (frame var-idx) (match frame ((,f ,@args) (def sig (fun-sig f)) (def arity (sig-arity sig)) (def nary? (sig-nary? sig)) (cond ((< var-idx arity) (elt args var-idx)) (nary? (sub* args arity)))))) (df frame-var-names (frame) (match frame ((,f ,@_) (fun-info-names (fun-info f))))) (df frame-var-values (frame) (map (curry frame-var-value frame) (keys (frame-var-names frame)))) (df do-user-frames (f|<fun> top-frame) (let ((idx -1) (top-seen? #f)) (do-stack-frames (fun (ffun args) (cond (top-seen? (incf idx) (f idx ffun (rev args))) ((= (pair ffun args) top-frame) (set top-seen? #t))))))) ;;;; Write some classes a little less verbose ;; (dm recurring-write (port|<out-port> x d|<int> recur|<fun>) ;; (msg port "#{%s &%s}" (class-name-str x) ;; (num-to-str-base (address-of x) 16))) (dm recurring-write (port|<out-port> x|<module> d|<int> recur|<fun>) (msg port "#{%s %s}" (class-name-str x) (module-name x))) (dm recurring-write (port|<out-port> x|<module-binding> d|<int> recur|<fun>) (msg port "#{%s %s}" (class-name-str x) (binding-name x))) (dm recurring-write (port|<out-port> x|<tab> d|<int> recur|<fun>) (msg port "#{%s %s}" (class-name-str x) (len x))) (dm recurring-write (port|<out-port> x|<static-global-environment> d|<int> recur|<fun>) (msg port "#{%s}" (class-name-str x))) (dm recurring-write (port|<out-port> x|<regular-application> d|<int> recur|<fun>) (msg port "#{%s}" (class-name-str x))) (dm recurring-write (port|<out-port> x|<src-loc> d|<int> recur|<fun>) (msg port "#{%s %s:%=}" (class-name-str x) (src-loc-file x) (src-loc-line x))) ;;;; Inspector (dc <inspector> (<any>)) (dp! @object (<inspector> => <any>)) (dp! @parts (<inspector> => <vec>) (new <vec>)) (dp! @stack (<inspector> => <lst>) '()) (dv inspector #f) (defslimefun init-inspector (form|<str>) (reset-inspector) (inspect-object (str-eval form (buffer-module)))) (defslimefun quit-inspector () (reset-inspector) 'nil) (defslimefun inspect-nth-part (n|<int>) (inspect-object (elt (@parts inspector) n))) (defslimefun inspector-pop () (cond ((<= 2 (len (@stack inspector))) (popf (@stack inspector)) (inspect-object (popf (@stack inspector)))) (#t 'nil))) (df reset-inspector () (set inspector (new <inspector>))) (df inspect-object (o) (set (@object inspector) o) (set (@parts inspector) (new <vec>)) (pushf (@stack inspector) o) (lst ':title (safe-write-to-string o) ; ':type (class-name-str o) ':content (inspector-content `("class: " (:value ,(class-of o)) "\n" ,@(inspect o))))) (df inspector-content (content) (map (fun (part) (case-by part isa? ((<str>) part) ((<lst>) (match part ((:value ,o ,@str) `(:value ,@(if (nul? str) (lst (safe-write-to-string o)) str) ,(assign-index o))))) (#t (error "Bad inspector content: %=" part)))) content)) (df assign-index (o) (pushf (@parts inspector) o) (1- (len (@parts inspector)))) (dg inspect (o)) ;; a list of dangerous functions (d. getter-blacklist (lst fun-code fun-env class-row)) (dm inspect (o) (join (map (fun (p) (let ((getter (prop-getter p))) `(,(sym-name (fun-name getter)) ": " ,(cond ((mem? getter-blacklist getter) "<...>") ((not (prop-bound? o getter)) "<unbound>") (#t (try-or (fun () `(:value ,(getter o))) "<...>")))))) (class-props (class-of o))) '("\n"))) (dm inspect (o|<seq>) (join (packing (do-keyed (fun (pos val) (pack `(,(num-to-str pos) ": " (:value ,val)))) o)) '("\n"))) (dm inspect (o|<tab>) (join (packing (do-keyed (fun (key val) (pack `((:value ,key) "\t: " (:value ,val)))) o)) '("\n"))) ;; inspecting the env of closures is broken ;; (dm inspect (o|<met>) ;; (cat (sup o) ;; '("\n") ;; (if (%fun-env? o) ;; (inspect (packing (for ((i (below (%fun-env-len o)))) ;; (pack (%fun-env-elt o i))))) ;; '()))) ;; ;; (df %fun-env? (f|<met> => <log>) #eb{ FUNENV($f) != $#f }) ;; (df %fun-env-len (f|<met> => <int>) #ei{ ((ENV)FUNENV ($f))->size }) ;; (df %fun-env-elt (f|<met> i|<int> => <any>) #eg{ FUNENVGET($f, @i) }) ;;;; init (defslimefun connection-info () `(:pid ,(process-id) :style nil :lisp-implementation (:type "GOO" :name "goo" :version ,(%lookup '*goo-version* 'eval/main)) :machine (:instance "" :type "" :version "") :features () :package (:name "goo/user" :prompt "goo/user"))) (defslimefun quit-lisp () #ei{ exit (0),0 }) (defslimefun set-default-directory (dir|<str>) #ei{ chdir(@dir) } dir) ;;;; eval (defslimefun ping () "PONG") (defslimefun create-repl (_) (let ((name (sym-name (module-name (buffer-module))))) `(,name ,name))) (defslimefun listener-eval (string) (clear-input in) `(:values ,(write-to-string (str-eval string (buffer-module))))) (defslimefun interactive-eval (string) (cat "=> " (write-to-string (str-eval string (buffer-module))))) (df str-eval (s|<str> m|<module>) (eval (read-from-string s) (module-name m))) (df clear-input (in|<in-port>) (while (ready? in) (get in))) (dc <break> (<restart>)) (defslimefun simple-break () (simple-restart <break> "Continue from break" (fun () (sig (new <simple-condition> condition-message "Interrupt from Emacs")))) 'nil) (defslimefun clear-repl-results () 'nil) ;;;; compile (defslimefun compile-string-for-emacs (string buffer position directory) (def start (current-time)) (def r (g2c-eval (read-from-string string) (module-target-environment (buffer-module)))) (lst (write-to-string r) (/ (as <flo> (- (current-time) start)) 1000000.0))) (defslimefun compiler-notes-for-emacs () 'nil) (defslimefun filename-to-modulename (filename|<str> => (t+ <str> <nil>)) (try-or (fun () (sym-name (filename-to-modulename filename))) 'nil)) (df filename-to-modulename (filename|<str> => <sym>) (def paths (map pathname-to-components (map simplify-filename (pick file-exists? *module-search-path*)))) (def filename (pathname-to-components filename)) (def moddir (rep parent ((modpath filename)) (cond ((any? (curry = modpath) paths) modpath) (#t (parent (components-parent-directory modpath)))))) (def modfile (components-to-pathname (sub* filename (len moddir)))) (as-sym (sub modfile 0 (- (len modfile) (len *goo-extension*))))) ;;;; Load (defslimefun load-file (filename) (let ((file (cond ((= (sub (rev filename) 0 4) "oog.") filename) (#t (cat filename ".goo"))))) (safe-write-to-string (load-file file (filename-to-modulename file))))) ;;;; background activities (defslimefun operator-arglist (op _) (try-or (fun () (let ((value (str-eval op (buffer-module)))) (if (isa? value <fun>) (write-to-string value) 'nil))) 'nil)) ;;;; M-. (defslimefun find-definitions-for-emacs (name|<str>) (match (parse-symbol name) ((,sym ,modname) (def env (module-target-environment (runtime-module modname))) (def b (find-binding sym env)) (cond (b (find-binding-definitions b)) (#t 'nil))))) (df parse-symbol (name|<str> => <lst>) (if (mem? name #\:) (match (split name #\:) ((,module ,name) (lst (as-sym name) (as-sym module)))) (lst (as-sym name) (module-name (buffer-module))))) (df find-binding-definitions (b|<binding>) (def value (case (binding-kind b) (('runtime) (loc-val (binding-locative b))) (('global) (let ((box (binding-global-box b))) (and box (global-box-value box)))) (('macro) (binding-info b)) (#t (error "unknown binding kind %=" (binding-kind b))))) (map (fun (o) (def loc (emacs-src-loc o)) `(,(write-to-string (dspec o)) ,(or loc `(:error "no src-loc available")))) (defining-objects value))) (dm defining-objects (o => <lst>) '()) (dm defining-objects (o|<fun> => <lst>) (lst o)) (dm defining-objects (o|<gen> => <lst>) (pair o (fun-mets o))) (dm emacs-src-loc (o|<fun>) (def loc (fun-src-loc o)) (and loc `(:location (:file ,(simplify-filename (find-goo-file-in-path (module-name-to-relpath (src-loc-file loc)) *module-search-path*))) (:line ,(src-loc-line loc)) ()))) (dm dspec (f|<fun>) (cond ((fun-name f) `(,(if (isa? f <gen>) 'dg 'dm) ,(fun-name f) ,@(dspec-arglist f))) (#t f))) (df dspec-arglist (f|<fun>) (map2 (fun (name class) (cond ((= class <any>) name) ((isa? class <class>) `(,name ,(class-name class))) (#t `(,name ,class)))) (fun-info-names (fun-info f)) (sig-specs (fun-sig f)))) (defslimefun buffer-first-change (filename) 'nil) ;;;; apropos (defslimefun apropos-list-for-emacs (pattern only-external? case-sensitive? package) (def matches (fab <tab> 100)) (do-all-bindings (fun (b) (when (finds (binding-name-str b) pattern) (set (elt matches (cat-sym (binding-name b) (module-name (binding-module b)))) b)))) (set matches (sort-by (packing-as <vec> (for ((b matches)) (pack b))) (fun (x y) (< (binding-name x) (binding-name y))))) (map (fun (b) `(:designator ,(cat (sym-name (module-name (binding-module b))) ":" (binding-name-str b) "\tkind: " (sym-name (binding-kind b))))) (as <lst> matches))) (df do-all-bindings (f|<fun>) (for ((module (%module-loader-modules (runtime-module-loader)))) (do f (environment-bindings (module-target-environment module))))) (dm < (s1|<str> s2|<str> => <log>) (let ((l1 (len s1)) (l2 (len s2))) (rep loop ((i 0)) (cond ((= i l1) (~= l1 l2)) ((= i l2) #f) ((< (elt s1 i) (elt s2 i)) #t) ((= (elt s1 i) (elt s2 i)) (loop (1+ i))) (#t #f))))) (df %binding-info (name|<sym> module|<sym>) (binding-info (find-binding name (module-target-environment (runtime-module module))))) ;;;; completion (defslimefun simple-completions (pattern|<str> package) (def matches (lst)) (for ((b (environment-bindings (module-target-environment (buffer-module))))) (when (prefix? (binding-name-str b) pattern) (pushf matches b))) (def strings (map binding-name-str matches)) `(,strings ,(cond ((nul? strings) pattern) (#t (fold+ common-prefix strings))))) (df common-prefix (s1|<seq> s2|<seq>) (let ((limit (min (len s1) (len s2)))) (rep loop ((i 0)) (cond ((or (= i limit) (~= (elt s1 i) (elt s2 i))) (sub s1 0 i)) (#t (loop (1+ i))))))) (defslimefun list-all-package-names (_|...) (map sym-name (keys (all-modules)))) (df all-modules () (%module-loader-modules (runtime-module-loader))) ;;;; Macroexpand (defslimefun swank-macroexpand-1 (str|<str>) (write-to-string (%ast-macro-expand (read-from-string str) (module-target-environment (buffer-module)) #f))) ;;;; streams (dc <slime-out-port> (<out-port>)) (dp @socket (<slime-out-port> => <port>)) (dp! @buf-len (<slime-out-port> => <int>) 0) (dp @buf (<slime-out-port> => <vec>) (new <vec>)) (dp! @timestamp (<slime-out-port> => <int>) 0) (dm recurring-write (port|<out-port> x|<slime-out-port> d|<int> recur|<fun>) (msg port "#{%s buf-len: %s}" (class-name-str x) (@buf-len x))) (dm put (p|<slime-out-port> c|<chr>) (add! (@buf p) c) (incf (@buf-len p)) (maybe-flush p (= c #\newline))) (dm puts (p|<slime-out-port> s|<str>) (add! (@buf p) s) (incf (@buf-len p) (len s)) (maybe-flush p (mem? s #\newline))) (df maybe-flush (p|<slime-out-port> newline?|<log>) (and (or (> (@buf-len p) 4000) newline?) (> (- (current-time) (@timestamp p)) 100000) (force-out p))) (dm force-out (p|<slime-out-port>) (unless (zero? (@buf-len p)) (dispatch-event `(:write-string ,(%buf-to-str (@buf p))) (@socket p)) (set (@buf-len p) 0) (zap! (@buf p))) (set (@timestamp p) (current-time))) (df %buf-to-str (buf|<vec>) (packing-as <str> (for ((i buf)) (cond ((isa? i <str>) (for ((c i)) (pack c))) (#t (pack i)))))) (dc <slime-in-port> (<in-port>)) (dp @socket (<slime-in-port> => <port>)) (dp! @idx (<slime-in-port> => <int>) 0) (dp! @buf (<slime-in-port> => <str>) "") (df receive-input (p|<slime-in-port>) (dispatch-event `(:read-string ,0) (@socket p))) (dm get (p|<slime-in-port> => <chr>) (cond ((< (@idx p) (len (@buf p))) (def c (elt (@buf p) (@idx p))) (incf (@idx p)) c) (#t (def input (receive-input p)) (cond ((zero? (len input)) (eof-object)) (#t (set (@buf p) input) (set (@idx p) 0) (get p)))))) (dm ready? (p|<slime-in-port> => <log>) (< (@idx p) (len (@buf p)))) (dm peek (p|<slime-in-port> => <chr>) (let ((c (get p))) (unless (eof-object? c) (decf (@idx p))) c)) ;;;; Message encoding (df decode-message (port|<in-port>) (read-from-string (get-block port (read-message-length port)))) (df read-message-length (port) (or (str-to-num (cat "#x" (get-block port 6))) (error "can't parse message length"))) (df encode-message (message port) (let ((string (dlet ((*max-print-length* 1000000) (*max-print-depth* 1000000)) (write-to-string message)))) (puts port (encode-message-length (len string))) (puts port string) (force-out port))) (df encode-message-length (n) (loc ((hex (byte) (if (< byte #x10) (cat "0" (num-to-str-base byte 16)) (num-to-str-base byte 16))) (byte (i) (hex (& (>> n (* i 8)) 255)))) (cat (byte 2) (byte 1) (byte 0)))) ;;;; semi general utilities ;; Return the name of O's class as string. (df class-name-str (o => <str>) (sym-name (class-name (class-of o)))) (df binding-name-str (b|<binding> => <str>) (sym-name (binding-name b))) (df as-sym (str|<str>) (as <sym> str)) ;; Replace '//' in the middle of a filename with with a '/' (df simplify-filename (str|<str> => <str>) (match (pathname-to-components str) ((,hd ,@tl) (components-to-pathname (cons hd (del-vals tl 'root)))))) ;; Execute BODY and only if BODY exits abnormally execute RECOVER. (df try-recover (body recover) (let ((ok #f)) (fin (let ((val (body))) (set ok #t) val) (unless ok (recover))))) ;; like CL's IGNORE-ERRORS but return VALUE in case of an error. (df try-or (body|<fun> value) (esc ret (try <error> (fun (condition resume) (ret value)) (body)))) (df simple-restart (type msg body) (esc restart (try ((type type) (description msg)) (fun (c r) (restart #f)) (body)))) (df safe-write-to-string (o) (esc ret (try <error> (fun (c r) (ret (cat "#<error during write " (class-name-str o) ">"))) (write-to-string o)))) ;; Read a string of length COUNT. (df get-block (port|<in-port> count|<int> => <str>) (packing-as <str> (for ((i (below count))) (let ((c (get port))) (cond ((eof-object? c) (error "Premature EOF (read %d of %d)" i count)) (#t (pack c))))))) ;;;; import some internal bindings (df %lookup (name|<sym> module|<sym>) (loc-val (binding-locative (find-binding name (module-target-environment (runtime-module module)))))) (d. %handler-info (%lookup 'handler-info 'goo/conditions)) (d. %handler-condition-type (%lookup 'handler-condition-type 'goo/conditions)) (d. %do-handlers-of-type (%lookup 'do-handlers-of-type 'goo/conditions)) (d. %module-loader-modules (%lookup 'module-loader-modules 'eval/module)) (d. %ast-macro-expand (%lookup 'ast-macro-expand 'eval/ast)) ;;;; low level socket stuff ;;; this shouldn't be here #{ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <sys/time.h> /* convert a goo number to a C long */ static long g2i (P o) { return untag (o); } static int set_reuse_address (int socket, int value) { return setsockopt (socket, SOL_SOCKET, SO_REUSEADDR, &value, sizeof value); } static int bind_socket (int socket, int port) { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons (port); addr.sin_addr.s_addr = htonl (INADDR_ANY); return bind (socket, (struct sockaddr *)&addr, sizeof addr); } static int local_port (int socket) { struct sockaddr_in addr; socklen_t len = sizeof addr; int code = getsockname (socket, (struct sockaddr *)&addr, &len); return (code == -1) ? -1 : ntohs (addr.sin_port); } static int c_accept (int socket) { struct sockaddr_in addr; socklen_t len = sizeof addr; return accept (socket, (struct sockaddr *)&addr, &len); } static P tup3 (P e0, P e1, P e2) { P tup = YPPtfab ((P)3, YPfalse); YPtelt_setter (e0, tup, (P)0); YPtelt_setter (e1, tup, (P)1); YPtelt_setter (e2, tup, (P)2); return tup; } static P current_time (void) { struct timeval timeval; int code = gettimeofday (&timeval, NULL); if (code == 0) { return tup3 (YPib ((P)(timeval.tv_sec >> 24)), YPib ((P)(timeval.tv_sec & 0xffffff)), YPib ((P)(timeval.tv_usec))); } else return YPib ((P)errno); } } ;; Return the current time in microsecs (df current-time (=> <int>) (def t #eg{ current_time () }) (cond ((isa? t <int>) (error "%s" (strerror t))) (#t (+ (* (+ (<< (1st t) 24) (2nd t)) 1000000) (3rd t))))) (dm strerror (e|<int> => <str>) #es{ strerror (g2i ($e)) }) (dm strerror (e|(t= #f) => <str>) #es{ strerror (errno) }) (df checkr (value|<int>) (cond ((~== value -1) value) (#t (error "%s" (strerror #f))))) (df create-socket (port|<int> => <int>) (let ((socket (checkr #ei{ socket (PF_INET, SOCK_STREAM, 0) }))) (checkr #ei{ set_reuse_address (g2i ($socket), 1) }) (checkr #ei{ bind_socket (g2i ($socket), g2i ($port)) }) (checkr #ei{ listen (g2i ($socket), 1)}) socket)) (df %local-port (fd|<int>) (checkr #ei{ local_port (g2i ($fd)) })) (df %close (fd|<int>) (checkr #ei{ close (g2i ($fd)) })) (dc <fd-io-port> (<in-port> <out-port>)) (dp @fd (<fd-io-port> => <int>)) (dp @in (<fd-io-port> => <file-in-port>)) (dp @out (<fd-io-port> => <file-out-port>)) (dm recurring-write (port|<out-port> x|<fd-io-port> d|<int> recur|<fun>) (msg port "#{%s fd: %s}" (class-name-str x) (@fd x))) (dm get (port|<fd-io-port> => <chr>) (get (@in port))) (dm puts (port|<fd-io-port> s|<str>) (puts (@out port) s)) (dm force-out (port|<fd-io-port>) (force-out (@out port))) (dm fdopen (fd|<int> type|(t= <fd-io-port>) => <fd-io-port>) (new <fd-io-port> @fd fd @in (new <file-in-port> port-handle (%fdopen fd "r")) @out (new <file-out-port> port-handle (%fdopen fd "w")))) (df %fdopen (fd|<int> mode|<str> => <loc>) (def addr #ei{ fdopen (g2i ($fd), @mode) }) (when (zero? addr) (error "fdopen failed: %s" (strerror #f))) (%lb (%iu addr))) (df accept (socket|<int> => <fd-io-port>) (fdopen (checkr #ei{ c_accept (g2i ($socket)) }) <fd-io-port>)) (export start-swank create-server) ;;; swank-goo.goo ends here