Hello folks. I use SLIME for all my Common Lisp interaction. I recently implemented a custom prototype-based object system for a roguelike game project i'm doing. With the use of reader macros and some code walking, I can write methods like this:
(define-method equip cell (item &optional slot) (when [is-equipment item] (let ((match [equipment-match self item])) (setf (getf <equipment> (or slot (first match))) item) [add-category item :equipped] ;; remove from inventory [remove-item self item])))
The syntax is [method-name object &rest args] and <foo> represents (field-value self :foo)
Another example:
(define-method forward world (method-key &rest args) "Send unhandled messages to the player object. This is where most world computations start, because nothing happens in a roguelike until the user has pressed a key." (let ((player <player>) (phase-number <phase-number>)) (with-message-queue <message-queue> ;; send the message, possibly generating queued messages (apply #'send method-key player args) ;; process any messages that were generated [process-messages self] ;; update the player's action points [take-turn player phase-number] ;; if this is the player's last turn, begin the cpu phase ;; otherwise, stay in player phase and exit (unless [needs-turn player phase-number] [run-cpu-phase self]))) nil)
I would like this to work with SLIME's arguments list lookup and so on. My system retains all the documentation and arglist info at runtime so I could probably hook this up somehow.
Any suggestions on how I could make this work with SLIME?
David O'Toole dto@gnu.org writes:
I would like this to work with SLIME's arguments list lookup and so on. My system retains all the documentation and arglist info at runtime so I could probably hook this up somehow.
Any suggestions on how I could make this work with SLIME?
First make sure that you use the slime-autodoc contrib. Then make sure that the following is executed whenever you open one of your games' source files:
(modify-syntax-entry ?[ "(") (modify-syntax-entry ?] ")")
Now, we can customize the SWANK side. You can do that by using ~/.swank.lisp.
Basically all you have to do is to add an :AROUND method for the default method of COMPUTE-ENRICHED-DECODED-ARGLIST like this:
(defmethod compute-enriched-decoded-arglist :around (operator-form argument-forms) (unless (find-package "DTO") (return (call-next-method))) (let ((dto-method-arglist (dto:method-lambda-list operator-form))) (if dto-method-arglist (decode-arglist dto-method-arglist) (call-next-method))))
DTO:METHOD-LAMBDA-LIST should return the lambda-list of the method named by the passed symbol.
I think that should be it. If you encounter problems, let me now.
-T.
"Tobias C. Rittweiler" tcr@freebits.de writes:
(defmethod compute-enriched-decoded-arglist :around (operator-form argument-forms) (unless (find-package "DTO") (return (call-next-method))) (let ((dto-method-arglist (dto:method-lambda-list operator-form))) (if dto-method-arglist (decode-arglist dto-method-arglist) (call-next-method))))
Of course, you can't use the symbol dto:method-lambda-list directly, but should construct it at runtime using INTERN.
-T.
* David O'Toole [2008-08-20 09:15+0200] writes:
The syntax is [method-name object &rest args] and <foo> represents (field-value self :foo)
You probably need to write some parsing code for this on the Emacs side.
I would recommend to use short operator names instead of reader macros. My favorites are:
(! message object args ...) ; send message (@ slot object) ; fetch slot (@ slot) ; could be (@ slot self)
This has the advantage that all the sexp moving commands just work. I also found it quite hard to close parens for things ([(...)]). Typing three parens in a row ))) is much easier than the fiddling needed to properly match ( with ) and [ with ].
There is some code in contrib/swank-arglists.lisp which understands things like (make-instance 'class ...). That could probably be reused by adding methods for @ and !.
However, in the absence of static type information, it's almost impossible to deduce the arglist of those methods. It may work for methods with a unique name, but if different classes have methods with the same names (ala "add") it's hard to know which class should be used. Java IDEs can do this easily due to the static nature of the language, but it requires a lot of work for dynamic languages, like Smalltalk or Python.
Helmut.
Hello Helmut,
On Wed, 2008-08-20 at 14:43 +0200, Helmut Eller wrote:
I would recommend to use short operator names instead of reader macros.
Thanks for your comments and suggestions everyone! I will try things out and see if I can get a basic lambda list / docstring thing going.
I debated the syntax issue for a long time, with a few online friends, back before this object system was ported to Common Lisp. (It was originally in elisp with identical syntax.) And originally the project did use @ and so on. But I moved to the [method-name object foo bar blaz] for several reasons. As a lisp programmer my eye is naturally drawn at once to the leftmost token in a list so that I can start getting hold of the sentence. Seeing ! and @ all the time at that moment would seem to dull the habit and I found it somewhat annoying. Instead I put the operation in the leftmost spot, just like function calls. This is consistent with my reading and writing style.
(@ :slot) is still too verbose and uses a sublist. CLOS with-slots is better than this. using < and > may be jarring at first but I find that when slot references are visually distinct from ordinary variable references, one thinks about optimizing slot access, and this object system is designed for games and other performant interactive programs.
Besides. I've already written about 3000 lines of OO-style code using this custom object system (entitled "CLON"). So it would be pretty hard to change now anyway. But I'm pretty comfortable with it... matching things like (([([(([ is trivial with Emacs' various delimiter highlighting and navigational modes, show-paren for example, and with these configs in Emacs the syntax will work properly in Lisp mode:
;;; Font-locking
;; Put this in your emacs initialization file to get the highlighting: ;; (add-hook 'emacs-lisp-mode-hook #'clon-do-font-lock)
(defvar clon-font-lock-keywords `((,(rx (sequence "(" (group "define-method") (one-or-more space) (group (one-or-more (not (any space)))) (one-or-more space) (group (one-or-more (not (any space)))))) (1 font-lock-keyword-face) (2 font-lock-function-name-face) ;; this still doesn't work ;; properly. (3 font-lock-type-face)) (,(rx (sequence "(" (group "define-prototype") (one-or-more space) (group (one-or-more (not (any space)))))) (1 font-lock-keyword-face) (2 font-lock-type-face)) ; ("\<\(<[^<>]*>\)\>" (1 font-lock-preprocessor-face)) ("(.*\(>>\>\)" (1 font-lock-type-face))))
(defun clon-do-font-lock () (interactive) "Highlight the keywords used in prototype-oriented programming." (font-lock-add-keywords nil clon-font-lock-keywords))
;;; Bracket matching in Common Lisp mode
;; Matching square brackets are turned off by default in Common Lisp ;; mode. This will turn them back on, which makes it work with ;; paredit, show-paren, and highlight-parentheses modes.
(modify-syntax-entry ?[ "(]" lisp-mode-syntax-table) (modify-syntax-entry ?] ")[" lisp-mode-syntax-table)
There is some code in contrib/swank-arglists.lisp which understands things like (make-instance 'class ...). That could probably be reused by adding methods for @ and !.
However, in the absence of static type information, it's almost impossible to deduce the arglist of those methods. It may work for methods with a unique name, but if different classes have methods with the same names (ala "add") it's hard to know which class should be used. Java IDEs can do this easily due to the static nature of the language, but it requires a lot of work for dynamic languages, like Smalltalk or Python.
Helmut.
slime-devel site list slime-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/slime-devel