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.

Saturday, June 9

wxWidgets Documentation in Devhelp format

This post is what it says on the tin. I converted the wxWidgets help from htb to devhelp using this handy help converter. Here is the result. It needs to live somewhere where devhelp can find it: usually /usr/share/devhelp/books or ~/.devhelp/books, although the latter did not work for me


Why did I want devhelp and not HTB. Partly because devhelp integrates much more nicely with Gnome, but also because it made it trivial to get context - sensitive help out of Emacs with this little gem:




(defun devhelp-word-at-point ()
"runs devhelp"
(interactive)
(setq w (current-word))
(start-process-shell-command "devhelp" nil "devhelp" "-s" w))

Monday, June 4

Save Slime And Die

The best way to work with a Lisp is to leave it running all the time and hack on it bit by bit, building up your program bit by bit, doing mini-tests and off-the-cuff code as you go along and shaping it..


Except if you are like me, you might be working on a laptop with a limited battery life and have a strict time limit to your lisp hacking. It's bothered me that when working with SLIME the only way to save a core was to start a lisp in a terminal, load up your systems and your files, then dump and use that with C-u M-x slime, which lets you enter a command line to start your lisp.


It annoys me because if you are like me, your code doesn't necessarily capture the actual state of your core: it also contains half a dozen test variables and functions that you entered in the repl and are useful to have hang around. So I tried to find a way to dump and restore core from within slime. I came up with the following function



(defun save-slime-and-die (core-file-name)
;; close all
(mapcar #'(lambda (x) (swank::close-connection x)) swank::*connections*)
#+sb-thread
(dolist (thread (remove (swank::current-thread) (swank::all-threads)))
(swank::kill-thread thread))
(sleep 1)
(save-lisp-and-die core-file-name))

It's obviously for sbcl, and the gotcha is that it has to be executed in the *inferior-lisp* buffer, but it works..C-u M-x sbcl --core test.core brings back core dumped with (save-slime-and-die #P"test.core")

Thursday, May 3

CPP Evil

Possibly the most incomprehensible line of code I have ever written.


#define ALLOCATE_ENGINE_OBJECT(type) reinterpret_cast<Engine##type##Object*>(\
world.allocate<Engine##type##Object>()->init_object(\
type##Pool.allocate<Engine##type##Data>()))

Sunday, March 4

Unrealscript mode for Emacs

Edit: I've since abandoned this mode and moved to a simpler (but less buggy) version not based on cc-mode that lives here


;;; unrealscript-mode.el --- unrealscript mode derived from cc-mode

;; Author: 2007 John Connors
;; Maintainer: John Connors <johnc at yagc dot co dot uk>
;; Created: March 2007
;; Version: See cc-mode.el
;; Keywords: unrealscript cc-mode languages oop

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to
;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;;; Commentary:


;; Note: The interface used in this file requires CC Mode 5.30 or
;; later.

;;; Code:

(require 'cc-mode)

;; These are only required at compile time to get the sources for the
;; language constants. (The cc-fonts require and the font-lock
;; related constants could additionally be put inside an
;; (eval-after-load "font-lock" ...) but then some trickery is
;; necessary to get them compiled.)
(eval-when-compile
(require 'cc-langs)
(require 'cc-fonts))

(eval-and-compile
;; Make our mode known to the language constant system. Use Java
;; mode as the fallback for the constants we don't change here.
;; This needs to be done also at compile time since the language
;; constants are evaluated then.
(c-add-language 'unrealscript-mode 'java-mode))

;; unrealscript has no boolean but a string and a vector type.
(c-lang-defconst c-primitive-type-kwds
unrealscript
(append
'("bool" "name" "object" "actor" "string" "vector")
(delete "boolean"
;; Use append to not be destructive on the
;; return value below.
(append
;; Due to the fallback to Java, we need not give
;; a language to `c-lang-const'.
(c-lang-const c-primitive-type-kwds)
nil))))

(c-lang-defconst c-type-modifier-keywds
unrealscript
(append
'("config" "deprecated" "edfindable" "editconstarray"
"editinline" "export" "noexport" "globalconfig"
"localized" "const" "editconst" "input" "travel"
"skip" "export")
(c-lang-const c-type-modifier-keywds)
nil))


;; "Keywords introducing extra declaration specifiers in the region
;; between the header and the body \(i.e. the \"K&R-region\") in
;; declarations."

(c-lang-defconst c-postfix-decl-spec-kwds
unrealscript
(append
'("abstract" "native" "nativereplication" "nousercreate" "within"
"perobjectconfig" "transient" "noexport" "dependson" "exportstructs"
"cacheexempt" "hidedropdown" "parseconfig" "dontcollapsecategories"
"collapsecategories" "hidecategories" "showcategories" "placeable"
"notplaceable" "instanced")
(c-lang-const c-postfix-decl-spec-kwds)
nil))

;; "Keywords that may be followed by a comma separated list of type
;; identifiers, where each optionally can be prefixed by keywords. (Can
;; also be used for the special case when the list can contain only one
;; element.)"
(c-lang-defconst c-type-list-kwds
unrealscript
(cons "within"
(c-lang-const c-type-list-kwds)))

;; Function declarations begin with "function" in this language.
;; There's currently no special keyword list for that in CC Mode, but
;; treating it as a modifier works fairly well.
(c-lang-defconst c-modifier-kwds
unrealscript
(cons
"function"
(c-lang-const c-modifier-kwds)))


(c-lang-defconst c-block-decls-with-vars
unrealscript
(cons
"state"
(c-lang-const c-other-block-decl-kwds)))

;; "Keywords that may be followed by a parenthesis expression that doesn't
;; contain type identifiers."
(c-lang-defconst c-paren-nontype-kwds
unrealscript
(append
'("state" "var")
(c-lang-const c-paren-nontype-kwds)
nil))


;; "Keywords that may be followed by a parenthesis expression containing
;; type identifiers separated by arbitrary tokens."
(c-lang-defconst c-paren-type-kwds
unrealscript
(append
'("config" "dependson")
(c-lang-const c-paren-type-kwds)))

;; ;; No cpp in this language, but there's still a "#exec" directive to
;; ;; fontify. (The definitions for the extra keywords above are enough
;; ;; to incorporate them into the fontification regexps for types and
;; ;; keywords, so no additional font-lock patterns are required.)
;; (c-lang-defconst c-cpp-matchers
;; unrealscript
;; ;; Use the eval form for `font-lock-keywords' to be able to use
;; ;; the `c-preprocessor-face-name' variable that maps to a
;; ;; suitable face depending on the (X)Emacs version.
;; '(eval . (list "^\\s *\\(#exec\\)\\>\\(.*\\)"
;; (list 1 c-preprocessor-face-name)
;; '(2 font-lock-string-face))))

(defcustom unrealscript-font-lock-extra-types nil
"*List of extra types (aside from the type keywords) to recognize in Unrealscript mode.
Each list item should be a regexp matching a single identifier."
)

(defconst unrealscript-font-lock-keywords-1
(c-lang-const c-matchers-1 unrealscript)
"Minimal highlighting for UNREALSCRIPT mode.")

(defconst unrealscript-font-lock-keywords-2
(c-lang-const c-matchers-2 unrealscript)
"Fast normal highlighting for UNREALSCRIPT mode.")

(defconst unrealscript-font-lock-keywords-3
(c-lang-const c-matchers-3 unrealscript)
"Accurate normal highlighting for UNREALSCRIPT mode.")

(defvar unrealscript-font-lock-keywords unrealscript-font-lock-keywords-3
"Default expressions to highlight in UNREALSCRIPT mode.")

(defvar unrealscript-mode-syntax-table nil
"Syntax table used in unrealscript-mode buffers.")
(or unrealscript-mode-syntax-table
(setq unrealscript-mode-syntax-table
(funcall (c-lang-const c-make-mode-syntax-table unrealscript))))

(defvar unrealscript-mode-abbrev-table nil
"Abbreviation table used in unrealscript-mode buffers.")

(c-define-abbrev-table 'unrealscript-mode-abbrev-table
;; Keywords that if they occur first on a line might alter the
;; syntactic context, and which therefore should trig reindentation
;; when they are completed.
'(("else" "else" c-electric-continued-statement 0)
("while" "while" c-electric-continued-statement 0)))

(defvar unrealscript-mode-map (let ((map (c-make-inherited-keymap)))
;; Add bindings which are only useful for UNREALSCRIPT
map)
"Keymap used in unrealscript-mode buffers.")

(easy-menu-define unrealscript-menu unrealscript-mode-map "UNREALSCRIPT Mode Commands"
;; Can use `unrealscript' as the language for `c-mode-menu'
;; since its definition covers any language. In
;; this case the language is used to adapt to the
;; nonexistence of a cpp pass and thus removing some
;; irrelevant menu alternatives.
(cons "UNREALSCRIPT" (c-lang-const c-mode-menu unrealscript)))

;;;###Autoload
(add-to-list 'auto-mode-alist '("\\.uc\\'" . unrealscript-mode))

;;;###autoload
(defun unrealscript-mode ()
"Major mode for editing UNREALSCRIPT UnrealScript is a
Java-like object-orientated programming (OOP) language created by
Epic Games for programming in-game content for the UnrealEngine.

The hook `
c-mode-common-hook' is run with no args at mode
initialization, then `
unrealscript-mode-hook'.

Key bindings:
\\{unrealscript-mode-map}"

(interactive)
(kill-all-local-variables)
(c-initialize-cc-mode t)
(set-syntax-table unrealscript-mode-syntax-table)
(setq major-mode 'unrealscript-mode
mode-name "UnrealScript"
local-abbrev-table unrealscript-mode-abbrev-table
abbrev-mode t)
(use-local-map c-mode-map)
;; `c-init-language-vars' is a macro that is expanded at compile
;; time to a large `setq' with all the language variables and their
;; customized values for our language.
(c-init-language-vars unrealscript-mode)
;; `c-common-init' initializes most of the components of a CC Mode
;; buffer, including setup of the mode menu, font-lock, etc.
;; There's also a lower level routine `c-basic-common-init' that
;; only makes the necessary initialization to get the syntactic
;; analysis and similar things working.
(c-common-init 'unrealscript-mode)
(easy-menu-add unrealscript-menu)
(run-hooks 'c-mode-common-hook)
(run-hooks 'unrealscript-mode-hook)
(setq font-lock-keywords-case-fold-search t)
(c-update-modeline))


(provide 'unrealscript-mode)


Thursday, March 1

Isometric Projection Matrix

One thing that seems to get asked a lot is how to create an isometric style game using a 3d pipline and textured quads, instead of 2d sprite tiles. It is also a question that does not seem to get answered much, so I thought I would whip out a quick demonstration of how to do it with the Irrlicht Engine.


One thing to note is that this is not a textbook isometric projection which is a form of axonometric projection, but a fudge that makes the width and height of a cube in the viewport equal. This emulates tile based games nicely. Of course, with a "real" projection it might be possible to dynamically change the projection angle, which might be an interesting effect.




#include <irrlicht.h>

using namespace irr;

using namespace core;
using namespace scene;
using namespace video;
using namespace io;
using namespace gui;



int main()
{


IrrlichtDevice *device =
createDevice( video::EDT_SOFTWARE2, dimension2d<s32>(640, 480), 16,
false, false, false, 0);

device->setWindowCaption(L"Isometric Projection");

IVideoDriver* driver = device->getVideoDriver();


while(device->run())
{
/*
Anything can be drawn between a beginScene() and an endScene()
call. The beginScene clears the screen with a color and also
the depth buffer if wanted. Then we let the Scene Manager and
the GUI Environment draw their content. With the endScene()
call everything is presented on the screen.
*/

driver->beginScene(true, true, SColor(255,100,101,140));

/**
* we are not interested in this transform so let us set it to
* identity
*/

driver->setTransform(video::ETS_WORLD, core::matrix4());
driver->setTransform(video::ETS_VIEW, core::matrix4());

/**
* first portion of transform - in matrix form
* x' = (x - z)
* y' = y + 0.5 * ( x + z )
* this maps the z to the x and y axes in such a way that
* the result appears isometric.
*/

matrix4 projMatrix;
projMatrix.makeIdentity();
projMatrix.M[0] = 1.0f;
projMatrix.M[8] = -1.0f;
projMatrix.M[1] = 0.5f;
projMatrix.M[5] = 1.0f;
projMatrix.M[9] = 0.5f;
projMatrix.M[10] = 0.0;

/**
* second portion of transform -- scale to fit clipping
* volume. If the scale is 1.0f then the unit vectors fit the
* clipping volume. The volume is a cuboid, centered in the origin.
* The scaling will determine the size of this volume which will
* contain the portion of the world that we can see.
*/

f32 scale = 4.0f;
matrix4 clipMatrix;
clipMatrix.buildProjectionMatrixOrthoLH(2 * scale, 2 * scale ,-1 * scale, 2 * scale);


/**
* concatentate transform - we multiply transforms together in
* the opposite order to that which we would apply them to a
* vector because of the law of associativity applies to
* matrices, but not commutivity and NM != MN fun,
* eh..matrices..gotta luv em..
*/

projMatrix = clipMatrix * projMatrix;

/**
* ok we now have our projection matrix
*/

driver->setTransform(video::ETS_PROJECTION, projMatrix);


/**
* draw unit x, y, and z vectors in our new space
*/


// x axis is red
driver->draw3DLine(vector3df(-1.0, 0.0, 0.0),
vector3df(1.0, 0.0, 0.0),
SColor(255,255,0,0));

// y axis is green
driver->draw3DLine(vector3df(0.0, -1.0, 0.0),
vector3df(0.0, 1.0, 0.0),
SColor(255,0,255,0));

// z axis is blue
driver->draw3DLine(vector3df(0.0, 0.0, -1.0),
vector3df(0.0, 0.0, 1.0),
SColor(255,0,0,255));

/**
* done
*/

driver->endScene();
}

device->drop();

return 0;
}