Wednesday, June 17

Dungeon Generation Part 3 : Doors

Thinking about key points in positioning a room, I decided doors were a good idea, so I tried to code up a routine that would reasonably place doors. I interpolated about 30% up along a line and placed a small black perpendicular line prependicular to the line along the centre of the corridor. I went with the simple rule of thumb that a point that connected more than two corridors was the centre of the room. I got this:




Not bad, but not quite right. Probably the best way forward is to decide on an overall radius that will establish the size of the room and fit the doors to that.

Monday, June 15

Dungeon Generation Part Two: Corridors

And once the network is complete, we do corridors. This is straigtforward geometry: extrude two lines at either end of our connecting line, perpendicular to said line, then join them up. When you do that you get this:




A floorplan which should extrude nicely into the third dimension. However there's one more thing to do before we tackle this, and that is the actual rooms. Which is where things get a tad harder..

Thursday, June 11

Dungeon generation for fun and..well, fun...

Recently I was inspired by the PixelCity series of articles. I was impressed what could be achieved by judicious application of basic OpenGL and some clever aesthetic decisions. I'm inspired enough to try the same thing, only with dungeons. My weapons of choice are pyglet and Python.


The first and most obvious thing to try was to generate a network of corridors. Here's what I ended up with.



I generated this mainly by picking a point and then connecting it to one of it's closest neighbours. The points are initially distributed on a grid and peturbed by a random amount to make the layout less "manhattan" like. The order the points are picked in is related to the grid order, which is why you see a "spine" at the side of the dungeon. I thought this would make for interersting gameplay: monster - infested branches connected by a relatively safe trunk. So this is what I went with. In the next part, we will get some corridoor walls. Once I have a decent looking dungeon, I will release the source.

Saturday, February 7

Give it a kick, it might still be going..


Just to prove I haven't been completely inactive on the project front since my flirtation with Lisp (which isn't over yet, either, just maturing slowly), here's a screenshot of yet another thing. Basically this is a mesh importer for the excellent OGRE engine, powered by wxWidgets and The Open Asset Import Library. It doesn't handle textures or bones yet, but it does more than enough for now as the Open Asset Import Library imports an ungodly number of formats, it should be possible to magpie bits of free art from the web in whatever format and assemble them into a passable scene using this thing..

Tuesday, December 16

More Emacs Help

As well as keying the help provided by Google in Emacs to the major-mode, it's possible to use it with multiple libraries as well. For instance, the following snippet lets me look up documentation for one library (OGRE) by pressing 'o' and for another by pressing 'a' , rather like the Slime selector, discussed previously. It's a huseful way of pulling together doxygen API documentation when working on clonk-loads of libraries, as is all to typical of C++ or Java development.


(defun search-site-url (keyword &optional site inurl lucky)
"Do a Google search for KEYWORD. Restrict to SITE and INURL, if specified.
Jump to best match (I Feel Lucky) if LUCKY set.
"
;; (message (list keyword site inurl lucky))
(concat "http://www.google.com/"
(format "search?q=%s" (url-hexify-string keyword))
(if site (format "+site:%s" (url-hexify-string site)))
(if inurl (format "+inurl:%s" (url-hexify-string inurl)))
(if lucky "&btnI")))


(defun help-selector ()
(interactive)
(message "Select [%s]: "
(apply #'string (mapcar #'car help-selector-methods)))
(let* ((ch (save-window-excursion
(select-window (minibuffer-window))
(read-char)))
(params (find ch help-selector-methods :key #'car)))
(browse-url (apply #'search-site-url (thing-at-point 'symbol) (list (third params) (fourth params) (fifth params))))))

(defvar help-selector-methods '(( ?a "Assimp"
"assimp.sourceforge.net" "/lib_html/" t)
( ?o "Ogre"
"www.ogre3d.org" "/docs/api/html/" t)
( ?w "WxWidgets"
"docs.wxwidgets.org" "/stable/" t)))

(global-set-key [(shift f1)] 'help-selector)

Thursday, August 21

Lambda of the Daleks


As promised, the Daleks appear. They have been loaded dynamically from an md2 whilst the framework was running. Sometimes I find myself fighting my C++ reflexes. There's no need to bring the framework down in order to change it. It still seems like magic when I edit a function definition, compile it and *bam* the change appears in the game window...






...one day, all software will be developed this way. Onwards..

Sunday, August 17

And now for a bit of swank..


Now my testbed talks happily to emacs via slime. That's the "exploratory" part of "exploratory game developement.". Next up: Daleks...

And now for something completley different.


You guessed it - another testbed. This time, in 100% Common Lisp (SBCL), just for comparison.

Wednesday, August 13

More testbed


I've done some more playing around with that testbed, integrating ECL and G3Ds Shape API. It works ok, apart from a few wrinkles. It might even be the beginnings of a nice educational app for learning 3d transforms and the like. The main improvement is that the REPL gives you error feedback rather than crashing the app when you give it a bad form. A distinct must for interactivity...

Tuesday, July 22

What next?


Embedding ECLS in a 3D Engine - G3D - proved remarkably easy. The question is - now what. What should live on the Lisp side and what should live on the C++ side? Geometry for C++? AI for Lisp..what about collisions, then? Hmm..

Wednesday, July 16

Using Google for Context Sensitive Help in Emacs


Here's a hack that combines three of the most useful things in the known Universe: Google, Emacs and Firefox. Emacs 22.x has a standard method for invoking a browser: the browse URL function. To customise it use:


M-x customize-group browse-url
We are interested in the settings: Browse Url Browser Function which should be set to
browse-url-firefox
Then there is Browse Url Firefox Program which should be set to the full path of wherever firefox lives on your machine, and Browse Url Firefox New Window Is Tab which should be on to prevent multiple firefoxen cluttering up your windows when you try this.


Now that you have customized everything, you drop a crafted function into your .emacs and bind it to a key:



;; -- INTEGRATED HELP
(require 'url)
(defun search-site-url (site url keyword)
(concat "http://www.google.com/"
(format "search?q=%s+site:%s+inurl:%s&btnI"
(url-hexify-string keyword)
(url-hexify-string site)
(url-hexify-string url))))

(defun wxhelp ()
"Open a window showing the wxWidgets documentation for the word under the point"
(interactive)
(browse-url (search-site-url "docs.wxwidgets.org" "2\\\\.8\\\\.6"
(thing-at-point 'symbol))))

(global-set-key "\C-h\C-w" 'wxhelp)



Now, if you type Control H Control W while the cursor is over a symbol, Emacs will look it up via Google. For instance, if your cursor is over wxApp, by the magic of "I'm feeling Lucky", Google will find http://docs.wxwidgets.org/2.8.6/wx_wxapp.html which is the manual page for wxApp



Obviously the interesting function here is

site-search-url
which munges up an URL to feed to google to make it search a specific site for a keyword, filtering URLs that do not contain a certian string. The highly escaped \\\\ is to ensure that 2\.8\.6 appears in the final URL so that Google treats . as a literal.



The great utility of this hack is its universiality. It will work for any language or library with a sanely organised reference web site. Language specific lookup functions based on sites like cppreference.com are left as an exercise for the reader...

Friday, March 14

Emacs Selectors

One of the nicer things that comes with slime is the slime-selector: it's just a little buffer switching thing, that lets you press r to switch to the repl buffer, d to switch to the debugger buffer, s to switch to the scratch buffer and so forth. Bound to a function key, it's an extraordinarily convenient way of switching to a specific buffer.



Of course, it's geared to SLIME and Lisp, but when coding C++ on the day job I often find myself switching between code, the debugger, shell and compilation buffer, so without further ado, here is the C equivalent, which is easily hacked up to include other buffers, too: actually, I'm beginning to think something like this functionality should be in emacs by default, as it eliminates a lot of keyboard gymnastics.







;; slime selector clone for c ----------------------------------

(defvar c-selector-methods nil)

(defun c-selector ()
(interactive)
(message "Select [%s]: "
(apply #'string (mapcar #'car c-selector-methods)))
(let* ((ch (save-window-excursion
(select-window (minibuffer-window))
(read-char)))
(method (find ch c-selector-methods :key #'car)))
(cond ((null method)
(message (format "No method for charachter: %s" ch))
(ding)
(sleep-for 1)
(discard-input)
(c-selector))
(t
(funcall (third method))))))


(defmacro def-c-selector-method (key description &rest body)
`(setq c-selector-methods
(sort* (cons (list ,key ,description
(lambda ()
(let ((buffer (progn ,@body)))
(cond ((get-buffer buffer)
(switch-to-buffer buffer))
(t
(message "No such buffer")
(ding))))))
(remove* ,key c-selector-methods :key #'car))
#'< :key #'car)))

(def-c-selector-method ?d "GDB debugger buffer"
gud-comint-buffer)

(def-c-selector-method ?g "Grep buffer"
grep-last-buffer)

(def-c-selector-method ?l "Buffer List"
(let ((result (get-buffer "*buffer-selection*")))
(if result
result
((bs-show)
(get-buffer "*buffer-selection*")))))

(def-c-selector-method ?c "Compilation buffer"
(let ((result (get-buffer "*compilation*")))
(if result
result
(compilation-find-buffer))))

(def-c-selector-method ?s "Shell buffer"
(get-buffer "*shell*"))

Friday, February 22

ASDF for the slightly confused

Well, you have a shiny new lisp compiler/interpreter/environment on your machine, and like an eager and smart newbie you want to do something with it. Right there and then many lisp newbies go SPLAT and end up as a bug on a windscreen, later to be scraped off and fed to Guido's pet snake. So here is ASDF for confused newbies (a very different proposition from a dummy, who should go back to reading bright yellow paperbacks and programming Java in the enterprise shop window).


ASDF is about 500 lines of lisp source, and a dot asd file is the lisp equivalent of the ubqutious make; you can't do anything in a serious modern lisp environment without it, so lets take you through it step by step. An asd file describes a system that contains modules and components. Components are usually individual files. Modules are collections of files, usually in subdirectories. Let's look at an example dot asd file: this one is for cowl - a simple opengl gui written by William Robinson who is working on Cityscape, a promising
game found at this address.


(defsystem #:cowl-ftgl
:depends-on (#:cffi)
:description "FTGL Common Lisp wrapper for Cowl."
:components ((:module "src"
:components
((:file "cowl-ftgl")))))


The defsystem form then defines a system with one component which is a module called src which in turn contains one component in a file called cowl-ftgl, which will be a lisp file, naturally: since it's in a module called src, it will be loaded in the src directory below the directory that holds the dot asd file. Sometimes it is necessary to wrap the defsystem form in it's own package using defpackage and a throwaway package name. We do not do that here because we do not define any symbols that we would need to refer to externally, but we would need to if we were defining new component classes or methods to be used in the system, examples of which will follow later in the article.


Components, modules and systems are defined as CLOS objects in asdf.lisp, so of course they can be operated upon with generic functions, and there are quite a few that come with asdf. The key one is named with Arc-like gnomicness: "oos" - which I imagine is short for "Operate on System". It takes at least two parameters: the operation to perform and the system to perform it on.


The operation you are most likely to perfom is load-op: this loads and compiles a system, and the form is simply:


(asdf:oos 'asdf:load-op 'system-name)

Other operations are compile-op, and test-op: the latter will only have meaning if the author of your system has provided tests, as some packages do.


ASDF will search for systems using the list of functions referred to by *system-definition-search-functions* used to search for .asd files in the filesystem. By default this contains only one function, (sysdef-central-registry-search) which is perfectly adequate for file systems with symbolic links. The modus operandi on such system is to have a share/common-lisp/systems directory populated with symbolic links to dot asd files which may be downloaded and untarred in any part of the fs tree to which the user has access. Then the (sysdef-central-registry-search) scans the list of directories referred to by *central-registry* for dot asd files containing the system which it needs to operate on.



To put it another way, procedurally this translates to:


  1. Download your asdf package

  2. Unzip - say to ~/thinngy/cl-blab

  3. Note that ~/thinngy/cl-blab/cl-blab.asd is the system.

  4. Create a ~/share/common-lisp/systems directory for systems
  5. cd ~/share/common-lisp/system
  6. ln -s ~/thinggy/cl-blab/cl-blab.asd cl-blab.asd
  7. now fire up your lisp and enter..

    ;; for systems that don't do (require 'asdf)
    (load #"/path/to/asdf.lisp")

    ;; the / on the end of the path is important, don't forget it
    (push #"/home/me/share/common-lisp/systems/" asdf:*central-registry*)

    (asdf:oos 'asdf:load-op 'cl-blab)

For Windows the situation is sligtly more complicated: either you write your own search function *or*, more practically apply a patch that lets you use shortcuts in place of symlinks.



You'd be right to point out that this is on the laborious side, but the purpose of this article is to demonstrate how asdf *works*. There are two packages for automating lisp package and asdf system installation: asdf-install and clbuild, which I encourage newbies to investigate, in the usual sadistic exercise for the reader.



Why go through all this palaver with asdf? Well the beauty of asdf is that systems, components and modules are in fact CLOS objects, so we can do tricks like this:




(cffi:load-foreign-library
(make-pathname
:name "cftgl"
:type "so"
:directory (directory-namestring (asdf:component-pathname
(asdf:find-system :cowl-ftgl)))))

..which loads "clftgl.so" which is found in the same directory as the dot asd file containing the system definition. Highly useful when distributing a C shared lib that wraps a C++ library so that it can be called into from Lisp.


The fact that source files are component, lets us write specialist methods for different kinds of source file such as c-source-file, java-source-file, html-file (all defined in asdf.lisp) allows asdf to work as a make replacement, operating on non-lisp source, thus:



(defmethod output-files ((op compile-op) (c c-source-file))
(list (make-pathname :type "o" :defaults
(component-pathname c))))

(defmethod perform ((op compile-op) (c c-source-file))
(unless
(= 0 (run-shell-command "/usr/bin/gcc -fPIC -o ~S -c ~S"
(unix-name (car (output-files op c)))
(unix-name (component-pathname c))))
(error 'operation-error :operation op :component c)))

(defmethod perform ((operation load-op) (c c-source-file))
t)

There is an extended example of this kind of thing where asdf is extended to work with a fortran to lisp converter here.

To conclude: like many things in Lisp, asdf is prickly for newbies and takes time to get to know and use well. However, when you do you can do things with it for which you'd normally need another build system, independent of your language. As Brucio would observe: Lisp does not have this limitation and Common Lispers laugh at things like ant.

Friday, December 28

Hippie Expand and Autocompletion in Emacs

The Emacs way of doing auto - completion is known as Hippie Expand which is a function that uses a variety of methods to try and expand an incomplete word near the point. Why it's called Hippie Expand I have no idea. The name is a bit misleading; for a long time I thought it was related to zippy or pinhead. Perhaps it's something to do with Emacs being used and written by hippies, who knows?


The nice thing about hippie expand is that like many things in Emacs, it can be customized and expanded, via the make-hippie-expand function. For instance, here is the hippie-expand function I use for c-mode



(make-hippie-expand-function
'(try-expand-dabbrev-visible
try-expand-dabbrev-from-kill
try-expand-dabbrev-all-buffers
try-complete-file-name-partially
try-complete-file-name)))))

This function first tries to expand the thing found at the point as a dynamic abbreviation, which is based on a scan of existing text for words or symbols with the same beginning as the one at the point. For instance, if "catalogue" is in the buffer, and you expand "cat", then "catalogue" will be one of the expansions offered, which is useful if "catalogue" is a variable name you use a lot.


The hippie expand function is bound to a key (I use M-/) and each press of that key will cycle through the possible completions for you. It's not Intellisense, but it does cut down on typing if you use descriptive variable names.


It's also fairly trivial to write your own hippie-expand function. Here is an example of a hippie expand function which does completion based on a list of current tags - which are sometimes more useful than dabbrevs.



;; This is a simple function to return the point at the beginning of the symbol to be completed
(defun he-tag-beg ()
(let ((p
(save-excursion
(backward-word 1)
(point))))
p))

;; The actual expansion function
(defun try-expand-tag (old)
;; old is true if we have already attempted an expansion
(unless old
;; he-init-string is used to capture the string we are trying to complete
(he-init-string (he-tag-beg) (point))
;; he-expand list is the list of possible expansions
(setq he-expand-list (sort
(all-completions he-search-string 'tags-complete-tag) 'string-lessp)))
;; now we go through the list, looking for an expansion that isn't in the table of previously
;; tried expansions
(while (and he-expand-list
(he-string-member (car he-expand-list) he-tried-table))
(setq he-expand-list (cdr he-expand-list)))
;; if we didn't have any expansions left, reset the expansion list
(if (null he-expand-list)
(progn
(when old (he-reset-string))
())
;; otherwise offer the expansion at the head of the list
(he-substitute-string (car he-expand-list))
;; and put that expansion into the tried expansions list
(setq he-expand-list (cdr he-expand-list))
t))
;; done, now we just use it as a clause in our make-hippie-expand-function (as above)


Hippie-expand isn't the only auto-completion gizmo I work with in Emacs; there is also slime-complete-symbol which does symbol completion for my while I am working in SLIME. This can be neatly integrated into hippie-expand, like so..


;; hippie expand slime symbol
(defun he-slime-symbol-beg ()
(let ((p
(slime-symbol-start-pos)))
p))

(defun try-expand-slime-symbol (old)
(unless old
(he-init-string (he-slime-symbol-beg) (point))
(setq he-expand-list (sort
(car (slime-contextual-completions (slime-symbol-start-pos) (slime-symbol-end-pos))) 'string-lessp)))
(while (and he-expand-list
(he-string-member (car he-expand-list) he-tried-table))
(setq he-expand-list (cdr he-expand-list)))
(if (null he-expand-list)
(progn
(when old (he-reset-string))
())
(he-substitute-string (car he-expand-list))
(setq he-expand-list (cdr he-expand-list))
t))

EDIT: With newer (CVS and 3.0) versions of SLIME the api for symbol completion
has changed and the hippie-expand function for slime symbol completion is..


;; hippie expand slime symbol
(defun he-slime-symbol-beg ()
(let ((p
(slime-symbol-start-pos)))
p))

(defun try-expand-slime-symbol (old)
(unless old
(he-init-string (he-slime-symbol-beg) (point))
(setq he-expand-list (sort
(car (slime-simple-completions
(buffer-substring-no-properties (slime-symbol-start-pos) (slime-symbol-end-pos))))
'string-lessp)))
(while (and he-expand-list
(he-string-member (car he-expand-list) he-tried-table))
(setq he-expand-list (cdr he-expand-list)))
(if (null he-expand-list)
(progn
(when old (he-reset-string))
())
(he-substitute-string (car he-expand-list))
(setq he-expand-list (cdr he-expand-list))
t))

Thursday, December 20

Final Results of Informal Programming Language Comparison.

A recent post on programming.reddit.com about profanity in comments was highly amusing if you are childish enough to be amused by such things - I am, anyway - set me to thinking about the Google Code search thing and whether it could be used in any profitable way to compare programming languages. I quicky hit upon the hypothesis that programmers content with their language would be more likely to type comments along the lines of "this rocks" and ones more disaffected might type comments along the lines of "this sucks".


Hence, all that was required was to write a bit of code to tabutlate the search results by language and a clear winner would emerge. It did, and it wasn't the one I was expecting.


My final choice of metric was the ratio of incidence of the word "sucks" and the word "rocks" scaled by the frequency of a netural control word such as "okay" (and even so, poor old FORTH did not get a lookin as it got zero hits on any of these). I call this the suckage to rockage ration and henceforth regard it as the gold standard of programming metrics. Without further ado, here are the charts and the code.




Controversy is roughly the distance between suckage and rockage - high if the language arouses strong feelings, low if it's boring, staple stuff.




C!? I really didn't expect this. It might be due to the inability of the search to separate out C and C++. It's almost certianly due to a low suckage rather than a high rockage.



The code, which is Public Domain, if anyone is inclined to tinker.



(asdf:oos 'asdf:load-op 'drakma)
(asdf:oos 'asdf:load-op 's-xml)
(asdf:oos 'asdf:load-op 'clot)

(defpackage :code-index (:use :cl :clot :drakma :s-xml))

(in-package :code-index)

(defun code-search (regexp &key language license file package (output-type :sxml))
(let ((url (concatenate 'string
"http://google.com/codesearch/feeds/search?q="
(when language (concatenate 'string "lang:" language "+"))
(when license (concatenate 'string "license:" license "+"))
(when file (concatenate 'string "file:" file "+"))
(when package (concatenate 'string "package:" package "+"))
regexp)))
(multiple-value-bind (body-or-stream status-code headers uri stream must-close reason-phrase)
(drakma::http-request url
:force-binary t)
(declare (ignore headers must-close stream reason-phrase))
(format t "Request for ~A~%" uri)
(format t "Status code ~A~%" status-code)
(parse-xml-string (flexi-streams:octets-to-string body-or-stream :external-format (flexi-streams:make-external-format :utf-8)) ))))

;; google returns a malformed string every time - wallies!
;;(code-search "sucks" :language "pascal")

(defun code-search-hit-count (results)
(parse-integer (cadr (nth 5 results))))

(defun calculate-index-for-language (lang)
(format t "~&For Langauge : ~A~&" lang)
(let* ((control-index
(code-search-hit-count (code-search "okay" :language lang)))
(suckage-index
(/ (code-search-hit-count (code-search "sucks" :language lang))
control-index))
(rockage-index
(/ (code-search-hit-count (code-search "rocks" :language lang))
control-index)))
(format t "Suckage index ~D~&" suckage-index)
(format t "Rockage index ~D~&" rockage-index)
(format t "Controversy index ~F~&" (sqrt (+ (* rockage-index rockage-index) (* suckage-index suckage-index))))
;; ;; admittedly it's a bust if no one ever says that language Y sucks, but how probable is that ;-)
;; ;; but forth managed it...
(format t "Suckage/Rockage ratio ~D~&" (/ rockage-index suckage-index))
(list control-index suckage-index rockage-index
(sqrt (+ (* rockage-index rockage-index) (* suckage-index suckage-index)))
(/ rockage-index suckage-index))))


(defun compare-languages ()
(let* ((language-list '("c" "ruby" "perl" "pascal" "erlang" "javascript" "java" "smalltalk" "python" "lisp" "haskell" "ocaml"))
(language-results (mapcar #'calculate-index-for-language language-list))
(control-list (append (list "Frequency" "brown") (mapcar #'(lambda (x) (nth 0 x)) language-results)))
(suckage-list (append (list "Suckage" "red") (mapcar #'(lambda (x) (nth 1 x)) language-results)))
(rockage-list (append (list "Rockage" "green") (mapcar #'(lambda (x) (nth 2 x)) language-results)))
(controversy-list (append (list "Controversy" "blue") (mapcar #'(lambda (x) (nth 3 x)) language-results)))
(suckage/rockage-list (append (list "Rockage to Suckage ratio" "yellow") (mapcar #'(lambda (x) (nth 4 x)) language-results))))
(cl-gd:with-image* (640 480)
(fill-image 0 0 :color "white")
(plot-bar-chart (list suckage-list rockage-list controversy-list) :x-axis-labels language-list :bar-width .8 :vgrid t)
(cl-gd:write-image-to-file
(make-pathname :defaults clot-system:*base-directory* :name "languages" :type "png") :if-exists :supersede))
(cl-gd:with-image* (640 480)
(fill-image 0 0 :color "white")
(plot-bar-chart (list suckage/rockage-list) :x-axis-labels language-list :bar-width .8 :vgrid t)
(cl-gd:write-image-to-file
(make-pathname :defaults clot-system:*base-directory* :name "suckage-to-rockage" :type "png") :if-exists :supersede))))

Programming Language Comparison


First cut.

Tuesday, November 27

Devhelp Inform Designers Manual

Another devhelp file courtesy of pyhtmlhelp. This time it's the Inform Designers manual.. Yes, I'm writing interactive fiction, again..

Sunday, October 14

Change-class extensibility

I made an interesting minor discovery today: change-class is extensible in much the way
initialize-instance is...




CL-USER> (defclass test-class () ((a-slot :initarg :a-slot-value :initform 0)))
#
CL-USER> (defclass derived-class (test-class) ())
#
CL-USER> (defmethod update-instance-for-different-class :after ((old test-class) (new derived-class) &key fixup-information)
(format t "~A " fixup-information))
# DERIVED-CLASS) {A97F421}>
CL-USER> (make-instance 'test-class :a-slot-value 27)
#
CL-USER> (defparameter *test-instance* (make-instance 'test-class :a-slot-value 27))
*TEST-INSTANCE*
CL-USER> (change-class *test-instance* 'derived-class :fixup-information "Hello World")
Hello World
#
CL-USER>


I'm not sure I actually want to use this, but it's nice to know it's there..

Wednesday, August 29

Automating Visual Studio

Visual Studio is both a curse and a boon. A boon because it is a very good IDE for debugging. A curse because it's restricted to a single platform and tends to lock out third-party editors, such as Emacs, jEdit, SlickEdit, or whatever it is you use.


Most decent editors expect to be able to compile a file via a shell command, capture that commands output, and then scan the output for errors, enabling you to jump to the exact locaton. This can be via make, scons, and the regexp can be modified for different compilers.


However the 'Export Makefile' command dissapeared from Visual Studio with version six. The functionality to compile individual files from the command line has gone, leaving the non-Microsoft editor user with a dilemma. Whether to change their editing habits for the sake of a peaceful life with the GUI, or forsake the ability to compile individual files - whole projects can still be built via the DevEnv command.


However there is a nice Python hack that lets you get at the compiling functionality. You need to use the Python Win32 COM extensions to access Visual Stduio automation from Python. Micheal Graz created this script and tightly integrated it with Vim. I've taken out the vim-specific parts, and added only one Emacs specific part (in dte_get_file) to invoke Emacs when "getting" the current file from Visual Studio. The compilation output now goes to the command line rather than a vim quickfix buffer, so it should be possible to adapt this to your needs.



import os, sys, re, time, pywintypes, win32com.client

vsWindowKindTaskList = '{4A9B7E51-AA16-11D0-A8C5-00A0C921A4D2}'
vsWindowKindFindResults1 = '{0F887920-C2B6-11D2-9375-0080C747D9A0}'
vsWindowKindFindResults2 = '{0F887921-C2B6-11D2-9375-0080C747D9A0}'
vsWindowKindOutput = '{34E76E81-EE4A-11D0-AE2E-00A0C90FFFC3}'

vsBuildStateNotStarted = 1 # Build has not yet been started.
vsBuildStateInProgress = 2 # Build is currently in progress.
vsBuildStateDone = 3 # Build has been completed

#----------------------------------------------------------------------

def dte_compile_file ():
dte = _get_dte()
if not dte: return
try:
dte.ExecuteCommand ('Build.Compile')
except Exception, e:
_dte_exception (e)
return
# ExecuteCommand is not synchronous so we have to wait
while dte.Solution.SolutionBuild.BuildState == vsBuildStateInProgress:
time.sleep (0.1)
dte_output ('output')
_status_msg ('Compile file complete')

#----------------------------------------------------------------------

def dte_build_solution():
dte = _get_dte()
if not dte: return
if dte.CSharpProjects.Count:
dte.Documents.CloseAll()
_dte_raise ()
_dte_output_activate ()
try:
dte.Solution.SolutionBuild.Build (1)
# Build is not synchronous so we have to wait
while dte.Solution.SolutionBuild.BuildState != vsBuildStateDone:
time.sleep (0.25)
except Exception, e:
_dte_exception (e)
return
dte_output ('output')
_status_msg ('Build solution complete')


#----------------------------------------------------------------------

def dte_output (window_kind):
if window_kind == 'find_results_1':
window_name = 'Find Results 1'
window_id = vsWindowKindFindResults1
elif window_kind == 'find_results_2':
window_name = 'Find Results 2'
window_id = vsWindowKindFindResults2
elif window_kind == 'output':
window_name = 'Output'
window_id = vsWindowKindOutput
else:
_msg ('Error: unrecognized window (%s)' % window_kind)
return
dte = _get_dte()
if not dte:
print ">> Failed to get dte."
return
if window_id == vsWindowKindOutput:
owp = dte.Windows.Item(window_id).Object.OutputWindowPanes.Item('Build')
sel = owp.TextDocument.Selection
else:
sel = dte.Windows.Item(window_id).Selection
sel.SelectAll()
_status_msg ('VS %s' % window_name)
print sel.Text
sel.Collapse()


#----------------------------------------------------------------------

def dte_get_file ():
dte = _get_dte()
if not dte: return
doc = dte.ActiveDocument
if not doc:
_status_msg ('No VS file!')
return
pt = doc.Selection.ActivePoint
file = os.path.join (doc.Path, doc.Name)
os.system("emacsclientw -n +%d:%d %s " % (pt.Line, pt.DisplayColumn, file))

#----------------------------------------------------------------------

def dte_put_file (filename, line_num, col_num):
if not filename:
return
dte = _get_dte()
if not dte: return
io = dte.ItemOperations
rc = io.OpenFile (os.path.abspath (filename))
sel = dte.ActiveDocument.Selection
sel.MoveToLineAndOffset (line_num, col_num)
_dte_raise ()

#----------------------------------------------------------------------

def _get_dte ():
try:
return win32com.client.GetActiveObject ('VisualStudio.DTE')
except pywintypes.com_error:
_msg ('Cannot access VisualStudio. Not running?')
return None

#----------------------------------------------------------------------

_wsh = None
def _get_wsh ():
global _wsh
if not _wsh:
try:
_wsh = win32com.client.Dispatch ('WScript.Shell')
except pywintypes.com_error:
_msg ('Cannot access WScript.Shell')
return _wsh

#----------------------------------------------------------------------


#----------------------------------------------------------------------

def _dte_raise ():
dte = _get_dte()
if not dte: return
try:
dte.MainWindow.Activate ()
_get_wsh().AppActivate (dte.MainWindow.Caption)
except:
pass

#----------------------------------------------------------------------

def _dte_output_activate ():
dte = _get_dte()
if not dte: return
dte.Windows.Item(vsWindowKindOutput).Activate()

#----------------------------------------------------------------------

def _dte_set_autoload ():
dte = _get_dte()
if not dte: return
p = dte.Properties ('Environment', 'Documents')
p.Item('DetectFileChangesOutsideIDE').Value = 1
p.Item('AutoloadExternalChanges').Value = 1


#----------------------------------------------------------------------

def _dte_exception (e):
if isinstance (e, pywintypes.com_error):
try:
msg = e[2][2]
except:
msg = None
else:
msg = e
if not msg:
msg = 'Encountered unknown exception'
_status_msg ('ERROR %s' % msg)

#----------------------------------------------------------------------

def _status_msg (msg):
try:
caption = _get_dte().MainWindow.Caption.split()[0]
except:
caption = None
if caption:
msg = msg + ' (' + caption + ')'
_msg (msg)

#----------------------------------------------------------------------

def _msg (msg):
print ">> " + msg


#----------------------------------------------------------------------
# eg vsgo.py dte_compile_file
# vsgo.py dte_put_file GridToolPanelComponent.cpp 10 5
# vsgo.py dte_get_file
# vsgo.py dte_build_solution

def main ():
prog = os.path.basename(sys.argv[0])
if len(sys.argv) == 1:
print 'echo "ERROR: not enough args to %s"' % prog
return

fcn_name = sys.argv[1]
if not globals().has_key(fcn_name):
print 'echo "ERROR: no such fcn %s in %s"' % (fcn_name, prog)
return

fcn = globals()[fcn_name]
try:
apply(fcn, sys.argv[2:])

except TypeError, e:
print 'echo "ERROR in %s: %s"' % (prog, str(e))
return

if __name__ == '__main__': main()

Tuesday, June 12

Lisp Array Setter Syntax

Not the most inituituve in the world



(defmethod (setf setter-name) (value (self class-type) x y z)
(setf (row-major-aref (array-of self) (* x ... blah... ))))


(setf (setter-name self) x-index y-index z-index value)

The thing to remember is that the value always comes first.