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