|
|
- ;;; swank-jolt.k --- Swank server for Jolt -*- goo -*-
- ;;
- ;; Copyright (C) 2008 Helmut Eller
- ;;
- ;; This file is licensed under the terms of the GNU General Public
- ;; License as distributed with Emacs (press C-h C-c for details).
-
- ;;; Commentary:
- ;;
- ;; Jolt/Coke is a Lisp-like language wich operates at the semantic level of
- ;; C, i.e. most objects are machine words and memory pointers. The
- ;; standard boot files define an interface to Id Smalltalk. So we can
- ;; also pretend to do OOP, but we must be careful to pass properly
- ;; tagged pointers to Smalltalk.
- ;;
- ;; This file only implements a minimum of SLIME's functionality. We
- ;; install a handler with atexit(3) to invoke the debugger. This way
- ;; we can stop Jolt from terminating the process on every error.
- ;; Unfortunately, the backtrace doesn't contain much information and
- ;; we also have no error message (other than the exit code). Jolt
- ;; usually prints some message to stdout before calling exit, so you
- ;; have to look in the *inferior-lisp* buffer for hints. We do
- ;; nothing (yet) to recover from SIGSEGV.
-
- ;;; Installation
- ;;
- ;; 1. Download and build cola. See <http://piumarta.com/software/cola/>.
- ;; I used the svn version:
- ;; svn co http://piumarta.com/svn2/idst/trunk idst
- ;; 2. Add something like this to your .emacs:
- ;;
- ;; (add-to-list 'slime-lisp-implementations
- ;; '(jolt (".../idst/function/jolt-burg/main"
- ;; "boot.k" ".../swank-jolt.k" "-") ; note the "-"
- ;; :init jolt-slime-init
- ;; :init-function slime-redirect-inferior-output)
- ;; (defun jolt-slime-init (file _) (format "%S\n" `(start-swank ,file)))
- ;; (defun jolt () (interactive) (slime 'jolt))
- ;;
- ;; 3. Use `M-x jolt' to start it.
- ;;
-
- ;;; Code
-
- ;; In this file I use 2-3 letters for often used names, like DF or
- ;; VEC, even if those names are abbreviations. I think that after a
- ;; little getting used to, this style is just as readable as the more
- ;; traditional DEFUN and VECTOR. Shorter names make it easier to
- ;; write terse code, in particular 1-line definitions.
-
- ;; `df' is like `defun' in a traditional lisp
- (syntax df
- (lambda (form compiler)
- (printf "df %s ...\n" [[[form second] asString] _stringValue])
- `(define ,[form second] (lambda ,@[form copyFrom: '2]))))
-
- ;; (! args ...) is the same as [args ...] but easier to edit.
- (syntax !
- (lambda (form compiler)
- (cond ((== [form size] '3)
- (if [[form third] isSymbol]
- `(send ',[form third] ,[form second])
- [compiler errorSyntax: [form third]]))
- ((and [[form size] > '3]
- (== [[form size] \\ '2] '0))
- (let ((args [OrderedCollection new])
- (keys [OrderedCollection new])
- (i '2) (len [form size]))
- (while (< i len)
- (let ((key [form at: i]))
- (if (or [key isKeyword]
- (and (== i '2) [key isSymbol])) ; for [X + Y]
- [keys addLast: [key asString]]
- [compiler errorSyntax: key]))
- [args addLast: [form at: [i + '1]]]
- (set i [i + '2]))
- `(send ',[[keys concatenated] asSymbol] ,[form second] ,@args)))
- (1 [compiler errorArgumentCount: form]))))
-
- (define Integer (import "Integer"))
- (define Symbol (import "Symbol")) ;; aka. _selector
- (define StaticBlockClosure (import "StaticBlockClosure"))
- (define BlockClosure (import "BlockClosure"))
- (define SequenceableCollection (import "SequenceableCollection"))
- (define _vtable (import "_vtable"))
- (define ByteArray (import "ByteArray"))
- (define CodeGenerator (import "CodeGenerator"))
- (define TheGlobalEnvironment (import "TheGlobalEnvironment"))
-
- (df error (msg) (! Object error: msg))
- (df print-to-string (obj)
- (let ((len '200)
- (stream (! WriteStream on: (! String new: len))))
- (! stream print: obj)
- (! stream contents)))
- (df assertion-failed (exp)
- (error (! '"Assertion failed: " , (print-to-string exp))))
-
- (syntax assert
- (lambda (form)
- `(if (not ,(! form second))
- (assertion-failed ',(! form second)))))
-
- (df isa? (obj type) (! obj isKindOf: type))
- (df equal (o1 o2) (! o1 = o2))
-
- (define nil 0)
- (define false 0)
- (define true (! Object notNil))
- (df bool? (obj) (or (== obj false) (== obj true)))
- (df int? (obj) (isa? obj Integer))
-
- ;; In this file the convention X>Y is used for operations that convert
- ;; X-to-Y. And _ means "machine word". So _>int is the operator that
- ;; converts a machine word to an Integer.
-
- (df _>int (word) (! Integer value_: word))
- (df int>_ (i) (! i _integerValue))
-
- ;; Fixnum operators. Manual tagging/untagging would probably be more
- ;; efficent than invoking methods.
-
- (df fix? (obj) (& obj 1))
- (df _>fix (n) (! SmallInteger value_: n))
- (df fix>_ (i) (! i _integerValue))
- (df fx+ (fx1 fx2) (! fx1 + fx2))
- (df fx* (fx1 fx2) (! fx1 * fx2))
- (df fx1+ (fx) (! fx + '1))
- (df fx1- (fx) (! fx - '1))
-
- (df str? (obj) (isa? obj String))
- (df >str (o) (! o asString))
- (df str>_ (s) (! s _stringValue))
- (df _>str (s) (! String value_: s))
- (df sym? (obj) (isa? obj Symbol))
- (df seq? (obj) (isa? obj SequenceableCollection))
- (df array? (obj) (isa? obj Array))
- (df len (obj) (! obj size))
- (df len_ (obj) (! (! obj size) _integerValue))
- (df ref (obj idx) (! obj at: idx))
- (df set-ref (obj idx elt) (! obj at: idx put: elt))
- (df first (obj) (! obj first))
- (df second (obj) (! obj second))
-
- (df puts (string stream) (! stream nextPutAll: string))
-
- (define _GC_base (dlsym "GC_base"))
-
- ;; Is ADDR a pointer to a heap allocated object? The Boehm GC nows
- ;; such things. This is useful for debugging, because we can quite
- ;; safely (i.e. without provoking SIGSEGV) access such addresses.
- (df valid-pointer? (addr)
- (let ((ptr (& addr (~ 1))))
- (and (_GC_base ptr)
- (_GC_base (long@ ptr -1)))))
-
- ;; Print OBJ as a Lisp printer would do.
- (df prin1 (obj stream)
- (cond ((fix? obj) (! stream print: obj))
- ((== obj nil) (puts '"nil" stream))
- ((== obj false) (puts '"#f" stream))
- ((== obj true) (puts '"#t" stream))
- ((not (valid-pointer? obj))
- (begin (puts '"#<w " stream)
- (prin1 (_>int obj) stream)
- (puts '">" stream)))
- ((int? obj) (! stream print: obj))
- ((sym? obj) (puts (>str obj) stream))
- ((isa? obj StaticBlockClosure)
- (begin (puts '"#<fun /" stream)
- (! stream print: (! obj arity))
- (puts '"#>" stream)))
- ((and (str? obj) (len obj))
- (! obj printEscapedOn: stream delimited: (ref '"\"" '0)))
- ((and (array? obj) (len obj))
- (begin (puts '"(" stream)
- (let ((max (- (len_ obj) 1)))
- (for (i 0 1 max)
- (prin1 (ref obj (_>fix i)) stream)
- (if (!= i max)
- (puts '" " stream))))
- (puts '")" stream)))
- ((and (isa? obj OrderedCollection) (len obj))
- (begin (puts '"#[" stream)
- (let ((max (- (len_ obj) 1)))
- (for (i 0 1 max)
- (prin1 (ref obj (_>fix i)) stream)
- (if (!= i max)
- (puts '" " stream))))
- (puts '"]" stream)))
- (true
- (begin (puts '"#<" stream)
- (puts (! obj debugName) stream)
- (puts '">" stream))))
- obj)
-
- (df print (obj)
- (prin1 obj StdOut)
- (puts '"\n" StdOut))
-
- (df prin1-to-string (obj)
- (let ((len '100)
- (stream (! WriteStream on: (! String new: len))))
- (prin1 obj stream)
- (! stream contents)))
-
- ;;(df %vable-tally (_vtable) (long@ _vtable))
- (df cr () (printf "\n"))
- (df print-object-selectors (obj)
- (let ((vtable (! obj _vtable))
- (tally (long@ vtable 0))
- (bindings (long@ vtable 1)))
- (for (i 1 1 tally)
- (print (long@ (long@ bindings i)))
- (cr))))
-
- (df print-object-slots (obj)
- (let ((size (! obj _sizeof))
- (end (+ obj size)))
- (while (< obj end)
- (print (long@ obj))
- (cr)
- (incr obj 4))))
-
- (df intern (string) (! Symbol intern: string))
-
- ;; Jolt doesn't seem to have an equivalent for gensym, but it's damn
- ;; hard to write macros without it. So here we adopt the conventions
- ;; that symbols which look like ".[0-9]+" are reserved for gensym and
- ;; shouldn't be used for "user visible variables".
- (define gensym-counter 0)
- (df gensym ()
- (set gensym-counter (+ gensym-counter 1))
- (intern (! '"." , (>str (_>fix gensym-counter)))))
-
- ;; Surprisingly, SequenceableCollection doesn't have a indexOf method.
- ;; So we even need to implement such mundane things.
- (df index-of (seq elt)
- (let ((max (len seq))
- (i '0))
- (while (! i < max)
- (if (equal (ref seq i) elt)
- (return i)
- (set i (! i + '1))))
- nil))
-
- (df find-dot (array) (index-of array '.))
-
- ;; What followes is the implementation of the pattern matching macro MIF.
- ;; The syntax is (mif (PATTERN EXP) THEN ELSE).
- ;; The THEN-branch is executed if PATTERN matches the value produced by EXP.
- ;; ELSE gets only executed if the match failes.
- ;; A pattern can be
- ;; 1) a symbol, which matches all values, but also binds the variable to the
- ;; value
- ;; 2) (quote LITERAL), matches if the value is `equal' to LITERAL.
- ;; 3) (PS ...) matches sequences, if the elements match PS.
- ;; 4) (P1 ... Pn . Ptail) matches if P1 ... Pn match the respective elements
- ;; at indices 1..n and if Ptail matches the rest
- ;; of the sequence
- ;; Examples:
- ;; (mif (x 10) x 'else) => 10
- ;; (mif ('a 'a) 'then 'else) => then
- ;; (mif ('a 'b) 'then 'else) => else
- ;; (mif ((a b) '(1 2)) b 'else) => 2
- ;; (mif ((a . b) '(1 2)) b 'else) => '(2)
- ;; (mif ((. x) '(1 2)) x 'else) => '(1 2)
-
- (define mif% 0) ;; defer
- (df mif%array (compiler pattern i value then fail)
- ;;(print `(mif%array ,pattern ,i ,value))
- (cond ((== i (len_ pattern)) then)
- ((== (ref pattern (_>fix i)) '.)
- (begin
- (if (!= (- (len_ pattern) 2) i)
- (begin
- (print pattern)
- (! compiler error: (! '"dot in strange position: "
- , (>str (_>fix i))))))
- (mif% compiler
- (ref pattern (_>fix (+ i 1)))
- `(! ,value copyFrom: ',(_>fix i))
- then fail)))
- (true
- (mif% compiler
- (ref pattern (_>fix i))
- `(ref ,value ',(_>fix i))
- (mif%array compiler pattern (+ i 1) value then fail)
- fail))))
-
- (df mif% (compiler pattern value then fail)
- ;;(print `(mif% ,pattern ,value ,then))
- (cond ((== pattern '_) then)
- ((== pattern '.) (! compiler errorSyntax: pattern))
- ((sym? pattern)
- `(let ((,pattern ,value)) ,then))
- ((seq? pattern)
- (cond ((== (len_ pattern) 0)
- `(if (== (len_ ,value) 0) ,then (goto ,fail)))
- ((== (first pattern) 'quote)
- (begin
- (if (not (== (len_ pattern) 2))
- (! compiler errorSyntax: pattern))
- `(if (equal ,value ,pattern) ,then (goto ,fail))))
- (true
- (let ((tmp (gensym)) (tmp2 (gensym))
- (pos (find-dot pattern)))
- `(let ((,tmp2 ,value)
- (,tmp ,tmp2))
- (if (and (seq? ,tmp)
- ,(if (find-dot pattern)
- `(>= (len ,tmp)
- ',(_>fix (- (len_ pattern) 2)))
- `(== (len ,tmp) ',(len pattern))))
- ,(mif%array compiler pattern 0 tmp then fail)
- (goto ,fail)))))))
- (true (! compiler errorSyntax: pattern))))
-
- (syntax mif
- (lambda (node compiler)
- ;;(print `(mif ,node))
- (if (not (or (== (len_ node) 4)
- (== (len_ node) 3)))
- (! compiler errorArgumentCount: node))
- (if (not (and (array? (ref node '1))
- (== (len_ (ref node '1)) 2)))
- (! compiler errorSyntax: (ref node '1)))
- (let ((pattern (first (ref node '1)))
- (value (second (ref node '1)))
- (then (ref node '2))
- (else (if (== (len_ node) 4)
- (ref node '3)
- `(error "mif failed")))
- (destination (gensym))
- (fail (! compiler newLabel))
- (success (! compiler newLabel)))
- `(let ((,destination 0))
- ,(mif% compiler pattern value
- `(begin (set ,destination ,then)
- (goto ,success))
- fail)
- (label ,fail)
- (set ,destination ,else)
- (label ,success)
- ,destination))))
-
- ;; (define *catch-stack* nil)
- ;;
- (df bar (o) (mif ('a o) 'yes 'no))
- (assert (== (bar 'a) 'yes))
- (assert (== (bar 'b) 'no))
- (df foo (o) (mif (('a) o) 'yes 'no))
- (assert (== (foo '(a)) 'yes))
- (assert (== (foo '(b)) 'no))
- (df baz (o) (mif (('a 'b) o) 'yes 'no))
- (assert (== (baz '(a b)) 'yes))
- (assert (== (baz '(a c)) 'no))
- (assert (== (baz '(b c)) 'no))
- (assert (== (baz 'a) 'no))
- (df mifvar (o) (mif (y o) y 'no))
- (assert (== (mifvar 'foo) 'foo))
- (df mifvec (o) (mif ((y) o) y 'no))
- (assert (== (mifvec '(a)) 'a))
- (assert (== (mifvec 'x) 'no))
- (df mifvec2 (o) (mif (('a y) o) y 'no))
- (assert (== (mifvec2 '(a b)) 'b))
- (assert (== (mifvec2 '(b c)) 'no))
- (assert (== (mif ((x) '(a)) x 'no) 'a))
- (assert (== (mif ((x . y) '(a b)) x 'no) 'a))
- (assert (== (mif ((x y . z) '(a b)) y 'no) 'b))
- (assert (equal (mif ((x . y) '(a b)) y 'no) '(b)))
- (assert (equal (mif ((. x) '(a b)) x 'no) '(a b)))
- (assert (equal (mif (((. x)) '((a b))) x 'no) '(a b)))
- (assert (equal (mif (((. x) . y) '((a b) c)) y 'no) '(c)))
- (assert (== (mif (() '()) 'yes 'no) 'yes))
- (assert (== (mif (() '(a)) 'yes 'no) 'no))
-
- ;; Now that we have a somewhat convenient pattern matcher we can write
- ;; a more convenient macro defining macro:
- (syntax defmacro
- (lambda (node compiler)
- (mif (('defmacro name (. args) . body) node)
- (begin
- (printf "defmacro %s ...\n" (str>_ (>str name)))
- `(syntax ,name
- (lambda (node compiler)
- (mif ((',name ,@args) node)
- (begin ,@body)
- (! compiler errorSyntax: node)))))
- (! compiler errorSyntax: node))))
-
- ;; and an even more convenient pattern matcher:
- (defmacro mcase (value . clauses)
- (let ((tmp (gensym)))
- `(let ((,tmp ,value))
- ,(mif (() clauses)
- `(begin (print ,tmp)
- (error "mcase failed"))
- (mif (((pattern . body) . more) clauses)
- `(mif (,pattern ,tmp)
- (begin ,@(mif (() body) '(0) body))
- (mcase ,tmp ,@more))
- (! compiler errorSyntax: clauses))))))
-
- ;; and some traditional macros
- (defmacro when (test . body) `(if ,test (begin ,@body)))
- (defmacro unless (test . body) `(if ,test 0 (begin ,@body)))
- (defmacro or (. args) ; the built in OR returns 1 on success.
- (mcase args
- (() 0)
- ((e) e)
- ((e1 . more)
- (let ((tmp (gensym)))
- `(let ((,tmp ,e1))
- (if ,tmp ,tmp (or ,@more)))))))
-
- (defmacro dotimes_ ((var n) . body)
- (let ((tmp (gensym)))
- `(let ((,tmp ,n)
- (,var 0))
- (while (< ,var ,tmp)
- ,@body
- (set ,var (+ ,var 1))))))
-
- (defmacro dotimes ((var n) . body)
- (let ((tmp (gensym)))
- `(let ((,tmp ,n)
- (,var '0))
- (while (< ,var ,tmp)
- ,@body
- (set ,var (fx1+ ,var))))))
-
- ;; DOVEC is like the traditional DOLIST but works on "vectors"
- ;; i.e. sequences which can be indexed efficently.
- (defmacro dovec ((var seq) . body)
- (let ((i (gensym))
- (max (gensym))
- (tmp (gensym)))
- `(let ((,i 0)
- (,tmp ,seq)
- (,max (len_ ,tmp)))
- (while (< ,i ,max)
- (let ((,var (! ,tmp at: (_>fix ,i))))
- ,@body
- (set ,i (+ ,i 1)))))))
-
- ;; "Packing" is what Lispers usually call "collecting".
- ;; The Lisp idiom (let ((result '())) .. (push x result) .. (nreverse result))
- ;; translates to (packing (result) .. (pack x result))
- (defmacro packing ((var) . body)
- `(let ((,var (! OrderedCollection new)))
- ,@body
- (! ,var asArray)))
-
- (df pack (elt packer) (! packer addLast: elt))
-
- (assert (equal (packing (p) (dotimes_ (i 2) (pack (_>fix i) p)))
- '(0 1)))
-
- (assert (equal (packing (p) (dovec (e '(2 3)) (pack e p)))
- '(2 3)))
-
- (assert (equal (packing (p)
- (let ((a '(2 3)))
- (dotimes (i (len a))
- (pack (ref a i) p))))
- '(2 3)))
-
- ;; MAPCAR (more or less)
- (df map (fun col)
- (packing (r)
- (dovec (e col)
- (pack (fun e) r))))
-
- ;; VEC allocates and initializes a new array.
- ;; The macro translates (vec x y z) to `(,x ,y ,z).
- (defmacro vec (. args)
- `(quasiquote
- (,@(map (lambda (arg) `(,'unquote ,arg))
- args))))
-
- (assert (equal (vec '0 '1) '(0 1)))
- (assert (equal (vec) '()))
- (assert (== (len (vec 0 1 2 3 4)) '5))
-
- ;; Concatenate.
- (defmacro cat (. args) `(! (vec '"" ,@args) concatenated))
-
- (assert (equal (cat '"a" '"b" '"c") '"abc"))
-
- ;; Take a vector of bytes and copy the bytes to a continuous
- ;; block of memory
- (df assemble_ (col) (! (! ByteArray withAll: col) _bytes))
-
- ;; Jolt doesn't seem to have catch/throw or something equivalent.
- ;; Here I use a pair of assembly routines as substitue.
- ;; (catch% FUN) calls FUN with the current stack pointer.
- ;; (throw% VALUE K) unwinds the stack to K and then returns VALUE.
- ;; catch% is a bit like call/cc.
- ;;
- ;; [Would setjmp/longjmp work from Jolt? or does setjmp require
- ;; C-compiler magic?]
- ;; [I figure Smalltalk has a way to do non-local-exits but, I don't know
- ;; how to use that in Jolt.]
- ;;
- (define catch%
- (assemble_
- '(0x55 ; push %ebp
- 0x89 0xe5 ; mov %esp,%ebp
- 0x54 ; push %esp
- 0x8b 0x45 0x08 ; mov 0x8(%ebp),%eax
- 0xff 0xd0 ; call *%eax
- 0xc9 ; leave
- 0xc3 ; ret
- )))
-
- (define throw%
- (assemble_
- `(,@'()
- 0x8b 0x44 0x24 0x04 ; mov 0x4(%esp),%eax
- 0x8b 0x6c 0x24 0x08 ; mov 0x8(%esp),%ebp
- 0xc9 ; leave
- 0xc3 ; ret
- )))
-
- (df bar (i k)
- (if (== i 0)
- (throw% 100 k)
- (begin
- (printf "bar %d\n" i)
- (bar (- i 1) k))))
- (df foo (k)
- (printf "foo.1\n")
- (printf "foo.2 %d\n" (bar 10 k)))
-
- ;; Our way to produce closures: we compile a new little function which
- ;; hardcodes the addresses of the code resp. the data-vector. The
- ;; nice thing is that such closures can be used called C function
- ;; pointers. It's probably slow to invoke the compiler for such
- ;; things, so use with care.
- (df make-closure (addr state)
- (int>_
- (! `(lambda (a b c d)
- (,(_>int addr) ,(_>int state) a b c d))
- eval)))
-
- ;; Return a closure which calls FUN with ARGS and the arguments
- ;; that the closure was called with.
- ;; Example: ((curry printf "%d\n") 10)
- (defmacro curry (fun . args)
- `(make-closure
- (lambda (state a b c d)
- ((ref state '0)
- ,@(packing (sv)
- (dotimes (i (len args))
- (pack `(ref state ',(fx1+ i)) sv)))
- a b c d))
- (vec ,fun ,@args)))
-
- (df parse-closure-arglist (vars)
- (let ((pos (or (index-of vars '|)
- (return nil)))
- (cvars (! vars copyFrom: '0 to: (fx1- pos)))
- (lvars (! vars copyFrom: (fx1+ pos))))
- (vec cvars lvars)))
-
- ;; Create a closure, to-be-closed-over variables must enumerated
- ;; explicitly.
- ;; Example: ((let ((x 1)) (closure (x | y) (+ x y))) 3) => 4.
- ;; The variables before the "|" are captured by the closure.
- (defmacro closure ((. vars) . body)
- (mif ((cvars lvars) (parse-closure-arglist vars))
- `(curry (lambda (,@cvars ,@lvars) ,@body)
- ,@cvars)
- (! compiler errorSyntax: vars)))
-
- ;; The analog for Smalltalkish "blocks".
- (defmacro block ((. vars) . body)
- (mif ((cvars lvars) (parse-closure-arglist vars))
- `(! StaticBlockClosure
- function_: (curry (lambda (,@cvars _closure _self ,@lvars) ,@body)
- ,@cvars)
- arity_: ,(len lvars))
- (! compiler errorSyntax: vars)))
-
- (define %mkstemp (dlsym "mkstemp"))
- (df make-temp-file ()
- (let ((name (! '"/tmp/jolt-tmp.XXXXXX" copy))
- (fd (%mkstemp (! name _stringValue))))
- (if (== fd -1)
- (error "mkstemp failed"))
- `(,fd ,name)))
- (define %unlink (dlsym "unlink"))
- (df unlink (filename) (%unlink (! filename _stringValue)))
-
- (define write (dlsym "write"))
- (df write-bytes (addr count fd)
- (let ((written (write fd addr count)))
- (if (!= written count)
- (begin
- (printf "write failed %p %d %d => %d" addr count fd written)
- (error '"write failed")))))
-
- (define system (dlsym "system"))
- (define main (dlsym "main"))
-
- ;; Starting at address ADDR, disassemble COUNT bytes.
- ;; This is implemented by writing the memory region to a file
- ;; and call ndisasm on it.
- (df disas (addr count)
- (let ((fd+name (make-temp-file)))
- (write-bytes addr count (first fd+name))
- (let ((cmd (str>_ (cat '"ndisasm -u -o "
- (>str (_>fix addr))
- '" " (second fd+name)))))
- (printf "Running: %s\n" cmd)
- (system cmd))
- (unlink (second fd+name))))
-
- (df rep ()
- (let ((result (! (! CokeScanner read: StdIn) eval)))
- (puts '"=> " StdOut)
- (print result)
- (puts '"\n" StdOut)))
-
- ;; Perhaps we could use setcontext/getcontext to return from signal
- ;; handlers (or not).
- (define +ucontext-size+ 350)
- (define _getcontext (dlsym "getcontext"))
- (define _setcontext (dlsym "setcontext"))
- (df getcontext ()
- (let ((context (malloc 350)))
- (_getcontext context)
- context))
-
- (define on_exit (dlsym "on_exit")) ; "atexit" doesn't work. why?
-
- (define *top-level-restart* 0)
- (define *top-level-context* 0)
- (define *debugger-hook* 0)
-
- ;; Jolt's error handling strategy is charmingly simple: call exit.
- ;; We invoke the SLIME debugger from an exit handler.
- ;; (The handler is registered with atexit, that's a libc function.)
-
- (df exit-handler (reason arg)
- (printf "exit-handler 0x%x\n" reason)
- ;;(backtrace)
- (on_exit exit-handler nil)
- (when *debugger-hook*
- (*debugger-hook* `(exit ,reason)))
- (cond (*top-level-context*
- (_setcontext *top-level-context*))
- (*top-level-restart*
- (throw% reason *top-level-restart*))))
-
- (df repl ()
- (set *top-level-context* (getcontext))
- (while (not (! (! StdIn readStream) atEnd))
- (printf "top-level\n")
- (catch%
- (lambda (k)
- (set *top-level-restart* k)
- (printf "repl\n")
- (while 1
- (rep)))))
- (printf "EOF\n"))
-
- ;; (repl)
-
-
- ;;; Socket code. (How boring. Duh, should have used netcat instead.)
-
- (define strerror (dlsym "strerror"))
-
- (df check-os-code (value)
- (if (== value -1)
- (error (_>str (strerror (fix>_ (! OS errno)))))
- value))
-
- ;; For now just hard-code constants which usually reside in header
- ;; files (just like a Forth guy would do).
- (define PF_INET 2)
- (define SOCK_STREAM 1)
- (define SOL_SOCKET 1)
- (define SO_REUSEADDR 2)
- (define socket (dlsym "socket"))
- (define setsockopt (dlsym "setsockopt"))
-
- (df set-reuse-address (sock value)
- (let ((word-size 4)
- (val (! Object _balloc: (_>fix word-size))))
- (set-int@ val value)
- (check-os-code
- (setsockopt sock SOL_SOCKET SO_REUSEADDR val word-size))))
-
- (define sockaddr_in/size 16)
- (define sockaddr_in/sin_family 0)
- (define sockaddr_in/sin_port 2)
- (define sockaddr_in/sin_addr 4)
- (define INADDR_ANY 0)
- (define AF_INET 2)
- (define htons (dlsym "htons"))
- (define bind (dlsym "bind"))
-
- (df bind-socket (sock port)
- (let ((addr (! OS _balloc: (_>fix sockaddr_in/size))))
- (set-short@ (+ addr sockaddr_in/sin_family) AF_INET)
- (set-short@ (+ addr sockaddr_in/sin_port) (htons port))
- (set-int@ (+ addr sockaddr_in/sin_addr) INADDR_ANY)
- (check-os-code
- (bind sock addr sockaddr_in/size))))
-
- (define listen (dlsym "listen"))
-
- (df create-socket (port)
- (let ((sock (check-os-code (socket PF_INET SOCK_STREAM 0))))
- (set-reuse-address sock 1)
- (bind-socket sock port)
- (check-os-code (listen sock 1))
- sock))
-
- (define accept% (dlsym "accept"))
- (df accept (sock)
- (let ((addr (! OS _balloc: (_>fix sockaddr_in/size)))
- (len (! OS _balloc: 4)))
- (set-int@ len sockaddr_in/size)
- (check-os-code (accept% sock addr len))))
-
- (define getsockname (dlsym "getsockname"))
- (define ntohs (dlsym "ntohs"))
- (df local-port (sock)
- (let ((addr (! OS _balloc: (_>fix sockaddr_in/size)))
- (len (! OS _balloc: 4)))
- (set-int@ len sockaddr_in/size)
- (check-os-code
- (getsockname sock addr len))
- (ntohs (short@ (+ addr sockaddr_in/sin_port)))))
-
- (define close (dlsym "close"))
- (define _read (dlsym "read"))
-
- ;; Now, after 2/3 of the file we can begin with the actual Swank
- ;; server.
-
- (df read-string (fd count)
- (let ((buffer (! String new: count))
- (buffer_ (str>_ buffer))
- (count_ (int>_ count))
- (start 0))
- (while (> (- count_ start) 0)
- (let ((rcount (check-os-code (_read fd
- (+ buffer_ start)
- (- count_ start)))))
- (set start (+ start rcount))))
- buffer))
-
- ;; Read and parse a message from the wire.
- (df read-packet (fd)
- (let ((header (read-string fd '6))
- (length (! Integer fromString: header base: '16))
- (payload (read-string fd length)))
- (! CokeScanner read: payload)))
-
- ;; Print a messag to the wire.
- (df send-to-emacs (event fd)
- (let ((stream (! WriteStream on: (! String new: '100))))
- (! stream position: '6)
- (prin1 event stream)
- (let ((len (! stream position)))
- (! stream position: '0)
- (! (fx+ len '-6) printOn: stream base: '16 width: '6)
- (write-bytes (str>_ (! stream collection)) (int>_ len) fd))))
-
- (df add-quotes (form)
- (mcase form
- ((fun . args)
- `(,fun ,@(packing (s)
- (dovec (e args)
- (pack `(quote ,e) s)))))))
-
- (define sldb 0) ;defer
-
- (df eval-for-emacs (form id fd abort)
- (let ((old-hook *debugger-hook*))
- (mcase (catch%
- (closure (form fd | k)
- (set *debugger-hook* (curry sldb fd k))
- `(ok ,(int>_ (! (add-quotes form) eval)))))
- (('ok value)
- (set *debugger-hook* old-hook)
- (send-to-emacs `(:return (:ok ,value) ,id) fd)
- 'ok)
- (arg
- (set *debugger-hook* old-hook)
- (send-to-emacs `(:return (:abort) ,id) fd)
- (throw% arg abort)))))
-
- (df process-events (fd)
- (on_exit exit-handler nil)
- (let ((done nil))
- (while (not done)
- (mcase (read-packet fd)
- ((':emacs-rex form package thread id)
- (mcase (catch% (closure (form id fd | abort)
- (eval-for-emacs form id fd abort)))
- ('ok)
- ;;('abort nil)
- ('top-level)
- (other
- ;;(return other) ; compiler breaks with return
- (set done 1))))))))
-
- (df next-frame (fp)
- (let ((next (get-caller-fp fp)))
- (if (and (!= next fp)
- (<= next %top-level-fp))
- next
- nil)))
-
- (df nth-frame (n top)
- (let ((fp top)
- (i 0))
- (while fp
- (if (== i n) (return fp))
- (set fp (next-frame fp))
- (set i (+ i 1)))
- nil))
-
- (define Dl_info/size 16)
- (define Dl_info/dli_fname 0)
- (define Dl_info/dli_sname 8)
-
- (df get-dl-sym-name (addr)
- (let ((info (! OS _balloc: (_>fix Dl_info/size))))
- (when (== (dladdr addr info) 0)
- (return nil))
- (let ((sname (long@ (+ info Dl_info/dli_sname)) )
- (fname (long@ (+ info Dl_info/dli_fname))))
- (cond ((and sname fname)
- (cat (_>str sname) '" in " (_>str fname)))
- (sname (_>str fname))
- (fname (cat '"<??> " (_>str fname)))
- (true nil)))))
-
- ;;(get-dl-sym-name printf)
-
- (df guess-function-name (ip)
- (let ((fname (get-function-name ip)))
- (if fname
- (_>str fname)
- (get-dl-sym-name ip))))
-
- (df backtrace>el (top_ from_ to_)
- (let ((fp (nth-frame from_ top_))
- (i from_))
- (packing (bt)
- (while (and fp (< i to_))
- (let ((ip (get-frame-ip fp)))
- (pack (vec (_>int i)
- (cat (or (guess-function-name ip) '"(no-name)")
- '" " ;;(>str (_>int ip))
- ))
- bt))
- (set i (+ i 1))
- (set fp (next-frame fp))))))
-
- (df debugger-info (fp msg)
- (vec `(,(prin1-to-string msg) " [type ...]" ())
- '(("quit" "Return to top level"))
- (backtrace>el fp 0 20)
- '()))
-
- (define *top-frame* 0)
- (define *sldb-quit* 0)
-
- (df debugger-loop (fd args abort)
- (let ((fp (get-current-fp)))
- (set *top-frame* fp)
- (send-to-emacs `(:debug 0 1 ,@(debugger-info fp args)) fd)
- (while 1
- (mcase (read-packet fd)
- ((':emacs-rex form package thread id)
- (mcase (catch% (closure (form id fd | k)
- (set *sldb-quit* k)
- (eval-for-emacs form id fd k)
- 'ok))
- ('ok nil)
- (other
- (send-to-emacs `(:return (:abort) ,id) fd)
- (throw% other abort))))))))
-
- (df sldb (fd abort args)
- (let ((old-top-frame *top-frame*)
- (old-sldb-quit *sldb-quit*))
- (mcase (catch% (curry debugger-loop fd args))
- (value
- (set *top-frame* old-top-frame)
- (set *sldb-quit* old-sldb-quit)
- (send-to-emacs `(:debug-return 0 1 nil) fd)
- (throw% value abort)))))
-
- (df swank:backtrace (start end)
- (backtrace>el *top-frame* (int>_ start) (int>_ end)))
-
- (df sldb-quit ()
- (assert *sldb-quit*)
- (throw% 'top-level *sldb-quit*))
-
- (df swank:invoke-nth-restart-for-emacs (...) (sldb-quit))
- (df swank:throw-to-toplevel (...) (sldb-quit))
-
- (df setup-server (port announce)
- (let ((sock (create-socket port)))
- (announce sock)
- (let ((client (accept sock)))
- (process-events client)
- (close client))
- (printf "Closing socket: %d %d\n" sock (local-port sock))
- (close sock)))
-
- (df announce-port (sock)
- (printf "Listening on port: %d\n" (local-port sock)))
-
- (df create-server (port) (setup-server port announce-port))
-
- (df write-port-file (filename sock)
- (let ((f (! File create: filename)))
- (! f write: (print-to-string (_>int (local-port sock))))
- (! f close)))
-
- (df start-swank (port-file)
- (setup-server 0 (curry write-port-file (_>str port-file))))
-
- (define getpid (dlsym "getpid"))
- (df swank:connection-info ()
- `(,@'()
- :pid ,(_>int (getpid))
- :style nil
- :lisp-implementation (,@'()
- :type "Coke"
- :name "jolt"
- :version ,(! CodeGenerator versionString))
- :machine (:instance "" :type ,(! OS architecture) :version "")
- :features ()
- :package (:name "jolt" :prompt "jolt")))
-
- (df swank:listener-eval (string)
- (let ((result (! (! CokeScanner read: string) eval)))
- `(:values ,(prin1-to-string (if (or (fix? result)
- (and (valid-pointer? result)
- (int? result)))
- (int>_ result)
- result))
- ,(prin1-to-string result))))
-
- (df swank:interactive-eval (string)
- (let ((result (! (! CokeScanner read: string) eval)))
- (cat '"=> " (prin1-to-string (if (or (fix? result)
- (and (valid-pointer? result)
- (int? result)))
- (int>_ result)
- result))
- '", " (prin1-to-string result))))
-
- (df swank:operator-arglist () nil)
- (df swank:buffer-first-change () nil)
- (df swank:create-repl (_) '("jolt" "jolt"))
-
- (df min (x y) (if (<= x y) x y))
-
- (df common-prefix2 (e1 e2)
- (let ((i '0)
- (max (min (len e1) (len e2))))
- (while (and (< i max)
- (== (ref e1 i) (ref e2 i)))
- (set i (fx1+ i)))
- (! e1 copyFrom: '0 to: (fx1- i))))
-
- (df common-prefix (seq)
- (mcase seq
- (() nil)
- (_
- (let ((prefix (ref seq '0)))
- (dovec (e seq)
- (set prefix (common-prefix2 prefix e)))
- prefix))))
-
- (df swank:simple-completions (prefix _package)
- (let ((matches (packing (s)
- (dovec (e (! TheGlobalEnvironment keys))
- (let ((name (>str e)))
- (when (! name beginsWith: prefix)
- (pack name s)))))))
- (vec matches (or (common-prefix matches) prefix))))
-
-
- ;; swank-jolt.k ends here
|