;;; 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
|