; malyon.el --- mode to execute z code files version 3, 5, 8

;; Maintainer: Peter Ilberg <peter.ilberg@gmail.com>
;; (I am unable to continue supporting malyon.el. Please send me an
;;  email if you are interested in taking over the project. Thanks.)

;; Copyright (C) 1999-2011 Peter Ilberg

;; Permission is hereby granted, free of charge, to any person obtaining a
;; copy of this software and associated documentation files (the "Software"),
;; to deal in the Software without restriction, including without limitation
;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
;; and/or sell copies of the Software, and to permit persons to whom the
;; Software is furnished to do so, subject to the following conditions:

;; The above copyright notice and this permission notice shall be included in
;; all copies or substantial portions of the Software.

;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
;; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;; DEALINGS IN THE SOFTWARE.

;;; Credits:

;;   The author would like to thank the following people for reporting
;;   bugs, testing, suggesting and/or contributing improvements:
;;     Bernhard Barde, Jonathan Craven, Alberto Petrofsky, Alan Shutko

;;; Commentary:

;; This package provides a basic interpreter for version 3, 5, 8 z code
;; story files as generated by Inform (C) Graham Nelson and Infocom.

;; If you encounter a bug please send a report to Peter Ilberg at 
;; peter.ilberg@gmail.com. Thank you!

;; To play a story file simple type M-x malyon and enter the path to the
;; story file. If anything goes wrong and you want to manually clean
;; up type M-x malyon-quit. In addition, you can switch back to a game in
;; progress by typing M-x malyon-restore.

;; A note on the format of saved game states:

;; As of version 1.0, Malyon supports the quetzal file format for saved 
;; games. Support for this format required changes to several internal
;; data structures (stack frames and catch-throw) that are incompatible
;; with the old implementation. Unfortunately, the old file format for
;; saved games cannot be converted into quetzal.

;; For backwards compatibility, however, Malyon still supports the old
;; file format. And you can continue to play your old game states.

;; Because of the incompatibility of the two file formats, Malyon now 
;; runs, as follows, in either of two modes: quetzal and compatibility.

;; - in quetzal mode, game states are saved in quetzal format
;; - in compatibility mode, games states are saved in the old format
;; - loading a game state in quetzal format switches to quetzal mode
;; - loading an old game state switches to compatibility mode
;; - quetzal mode is the default setting

;; In other words, Malyon will only use the old file format if you've
;; restored a game state saved in the old file format.

;; Enjoy!

;;; Code:

;; global variables - moved here to appease the byte-code compiler

;; story file information

(defvar malyon-story-file-name nil
  "The name of the story file being executed.")

(defvar malyon-story-file nil
  "The story file which is currently being run.")

(defvar malyon-story-version nil
  "The story file version.")

(defvar malyon-supported-versions '(3 5 8)
  "A list of supported story file versions.")

;; status and transcript buffers

(defvar malyon-transcript-buffer nil
  "The main transcript buffer of the story file execution.")

(defvar malyon-transcript-buffer-buffered nil
  "Is output in the transcript buffer buffered?")

(defvar malyon-status-buffer nil
  "The status bar buffer of the story file execution.")

(defvar malyon-status-buffer-lines nil
  "The number of lines in the status bar buffer.")

(defvar malyon-status-buffer-delayed-split nil
  "If the number of lines in the status buffer is reduced,
the window configuration is not changed immediately. It
is changed after the next turn (read or read_char).")

(defvar malyon-status-buffer-point nil
  "The point location in the status bar buffer.")

(defvar malyon-max-column 72
  "Maximum column for text display.")

;; window management

(defvar malyon-window-configuration nil
  "The current window configuration of the malyon interpreter.")

(defvar malyon-current-window nil
  "The currently active window for text output.")

;; z machine registers

(defvar malyon-stack nil
  "The stack of the z machine.")

(defvar malyon-stack-pointer nil
  "The stack pointer of the z machine.")

(defvar malyon-frame-pointer nil
  "The frame pointer of the z machine.")

(defvar malyon-instruction-pointer nil
  "The instruction pointer of the z machine.")

;; game file related global variables

(defvar malyon-score-game nil
  "A flag indicating whether this story uses score or time.")

(defvar malyon-packed-multiplier nil
  "The amount by which packed addresses are multiplied to get byte
addresses.")

(defvar malyon-global-variables nil
  "A pointer to the global variable section in the story file.")

(defvar malyon-abbreviations nil
  "A pointer to the abbreviations in the story file.")

(defvar malyon-alphabet nil
  "The z machine's text alphabet.")

(defvar malyon-whitespace nil
  "A string of whitespace characters recognized by the interpreter.")

;; object tables

(defvar malyon-object-table nil
  "A pointer to the object table in the story file.")

(defvar malyon-object-table-entry-size nil
  "The size of one entry in the object table.")

(defvar malyon-object-properties nil
  "The number of properties per object minus one.")

(defvar malyon-object-property-offset nil
  "The byte offset of the properties table in the object.")

;; dictionaries

(defvar malyon-dictionary nil
  "A pointer to the dictionary of the story file.")

(defvar malyon-dictionary-entry-length nil
  "The length of a dictionary entry.")

(defvar malyon-dictionary-num-entries nil
  "The number of dictionary entries.")

(defvar malyon-dictionary-entries nil
  "A pointer to the first dictionary entry.")

(defvar malyon-dictionary-word-length nil
  "The length of a dictionary word.")

;; game state information

(defvar malyon-game-state-restart nil
  "The machine state for implementing restart.")

(defvar malyon-game-state-undo nil
  "The machine state for implementing undo.")

(defvar malyon-game-state-quetzal t
  "Store game state information for quetzal.")

;; various

(defvar malyon-current-face nil
  "The current face in which to display text.")

(defvar malyon-last-cursor-position-after-input nil
  "The last cursor position after reading input from the keyboard.")

;; interactive functions

(defun malyon (file-name)
  "Major mode for playing z3/5/8 story files.
This mode allows execution of version 3, 5, 8 z code story files."
  (interactive "fStory file name: ")
  (if malyon-story-file
      (message "You are already playing a game.")
    (if (not (string-match ".*\.z[358]$" file-name))
  (message "%s is not a version 3, 5, or 8 story file." file-name)
      (condition-case nil
    (malyon-load-story-file file-name)
  (error
   (malyon-fatal-error "loading of story file failed.")))
      (setq malyon-story-version (aref malyon-story-file 0))
      (cond ((memq malyon-story-version malyon-supported-versions)
       (condition-case nil
     (malyon-initialize)
         (error
    (malyon-fatal-error "initialization of interpreter failed.")))
       (malyon-interpreter))
      (t
       (message "%s is not a version 3, 5, or 8 story file." file-name)
       (malyon-cleanup))))))

(defun malyon-restore ()
  "Restore the save window configuration for the interpreter."
  (interactive)
  (condition-case nil
      (progn
  (malyon-restore-window-configuration)
  (malyon-adjust-transcript))
    (error
     (malyon-fatal-error "restoring window configuration failed."))))

(defun malyon-quit ()
  "Exit the malyon interpreter."
  (interactive)
  (if malyon-story-file
      (progn
  (malyon-restore)
  (if (malyon-yes-or-no-p-minibuf "Do you really want to quit? ")
      (malyon-cleanup)))))

(defun malyon-mode ()
  "This mode provides a basic interpreter for version 3, 5, 8 z code
story files as generated by Inform (C) Graham Nelson and Infocom. 

Note that this package is by no means complete and bug free.
If you encounter a bug please send a report to Peter Ilberg at 
peter.ilberg@natinst.com. Thank you!

To play a story file simple type M-x malyon and enter the path to the
story file. If anything goes wrong and you want to manually clean
up type M-x malyon-quit. In addition, you can switch back to a game in
progress by typing M-x malyon-restore.

The author would like to thank the following people for reporting
bugs, testing, suggesting and/or contributing improvements:
    Bernhard Barde, Jonathan Craven, Alberto Petrofsky, Alan Shutko"
  (message "Use M-x malyon if you want to play a zcode game."))

;; compatibility functions for GNU emacs

(if (fboundp 'cadr)
    (defalias 'malyon-cadr 'cadr)
  (defun malyon-cadr (list)
    "Take the cadr of the list."
    (car (cdr list))))

(if (fboundp 'caddr)
    (defalias 'malyon-caddr 'caddr)
  (defun malyon-caddr (list)
    "Take the caddr of the list."
    (car (cdr (cdr list)))))

(if (fboundp 'cdddr)
    (defalias 'malyon-cdddr 'cdddr)
  (defun malyon-cdddr (list)
    "Take the cdddr of the list."
    (cdr (cdr (cdr list)))))

(if (fboundp 'char-before)
    (defalias 'malyon-char-before 'char-before)
  (defun malyon-char-before ()
    "Return the character before the point."
    (char-after (- (point) 1))))

(if (fboundp 'char-to-int)
    (defalias 'malyon-char-to-int 'char-to-int)
  (defun malyon-char-to-int (c)
    "Convert a character into an integer."
    c))

(if (fboundp 'characterp)
    (defalias 'malyon-characterp 'characterp)
  (defun malyon-characterp (x)
    "Test for a character."
    (and (numberp x) (<= 0 x) (< x 256))))

(defun malyon-disable-multibyte ()
  "Disable multibyte support in the current buffer."
  (condition-case nil (set-buffer-multibyte nil) (error)))

(defun malyon-erase-buffer (&optional buffer)
  "Erase the given buffer."
  (save-excursion
    (if buffer (set-buffer buffer))
    (if (and buffer (eq buffer malyon-transcript-buffer))
  (malyon-begin-section)
      (erase-buffer))))

(if (fboundp 'int-to-char)
    (defalias 'malyon-int-to-char 'int-to-char)
  (defun malyon-int-to-char (i)
    "Convert an integer into a character."
    i))

(if (fboundp 'mapc)
    (defalias 'malyon-mapc 'mapc)
  (defun malyon-mapc (function list)
    "Apply fun to every element of args ignoring the results."
    (if (null list)
  '()
      (funcall function (car list))
      (malyon-mapc function (cdr list)))))

(if (fboundp 'mapcan)
    (defalias 'malyon-mapcan 'mapcan)
  (defun malyon-mapcan (function list)
    "Apply fun to every element of args nconc'ing the result."
    (if (null list)
  '()
      (nconc (funcall function (car list))
       (malyon-mapcan function (cdr list))))))

; Do not use the built-in conversion via 'multibyte-char-to-unibyte.
(defun malyon-multibyte-char-to-unibyte (char)
  "Convert a multibyte character to unibyte."
  char)

(defun malyon-point-max (&optional buffer)
  "Get the point-max of the given buffer."
  (save-excursion
    (if buffer (set-buffer buffer))
    (point-max)))

(if (fboundp 'redisplay-frame)
    (defalias 'malyon-redisplay-frame 'redisplay-frame)
  (defun malyon-redisplay-frame (frame &rest ignore)
    "Redisplay the given frame."))

(if (fboundp 'remove)
    (defalias 'malyon-remove 'remove)
  (defun malyon-remove (element list)
    "Remove the element from the list."
    (cond ((null list)
     '())
    ((eq element (car list))
     (malyon-remove element (cdr list)))
    ((equal element (car list))
     (malyon-remove element (cdr list)))
    (t
     (cons (car list)
     (malyon-remove element (cdr list)))))))

(if (fboundp 'set-keymap-name)
    (defalias 'malyon-set-keymap-name 'set-keymap-name)
  (defun malyon-set-keymap-name (keymap name)
    "Set the name of the keymap."))

(if (fboundp 'string-to-list)
    (defalias 'malyon-string-to-list 'string-to-list)
  (defun malyon-string-to-list (s)
    "Convert a string into a list of characters."
    (let ((i (- (length s) 1)) (l '()))
      (while (<= 0 i)
  (setq l (cons (aref s i) l)
        i (- i 1)))
      l)))

(if (fboundp 'string-to-vector)
    (defalias 'malyon-string-to-vector 'string-to-vector)
  (defun malyon-string-to-vector (s)
    "Convert a string into a vector of characters."
    (let* ((i 0) (l (length s)) (v (make-vector l 0)))
      (while (< i l)
  (aset v i (aref s i))
  (setq i (+ 1 i)))
      v)))

; Do not use the built-in conversion via 'unibyte-char-to-multibyte.
(defun malyon-unibyte-char-to-multibyte (char)
  "Convert a unibyte character to multibyte."
  char)

(defun malyon-vector-to-list (v begin end)
  "Return a list of elements in v in the range [begin, end)."
  (let ((result '()))
    (while (< begin end)
      (setq result (cons (aref v begin) result))
      (setq begin (+ 1 begin)))
    (reverse result)))

(if (fboundp 'window-displayed-height)
    (defalias 'malyon-window-displayed-height 'window-displayed-height)
  (defun malyon-window-displayed-height (&optional window)
    "Get the height of the window's displayed region."
    (- (window-height) 1)))

(if (fboundp 'yes-or-no-p-minibuf)
    (defalias 'malyon-yes-or-no-p-minibuf 'yes-or-no-p-minibuf)
  (defun malyon-yes-or-no-p-minibuf (prompt)
    "Ask a yes or no question."
    (yes-or-no-p prompt)))

;; global variables for the malyon mode

(defvar malyon-syntax-table nil
  "Syntax table used while in malyon mode (same as in text-mode).")

(if malyon-syntax-table
    '()
  (setq malyon-syntax-table (make-syntax-table))
  (modify-syntax-entry ?\" ".   " malyon-syntax-table)
  (modify-syntax-entry ?\\ ".   " malyon-syntax-table)
  (modify-syntax-entry ?'  "w   " malyon-syntax-table))

(defvar malyon-keymap-read nil
  "Keymap for malyon mode for reading input into a buffer.")

(defvar malyon-history-saved-up nil
  "The saved binding for the up arrow key.")

(defvar malyon-history-saved-down nil
  "The saved binding for the down arrow key.")

(if malyon-keymap-read
    '()
  (setq malyon-keymap-read (make-sparse-keymap))
  (malyon-set-keymap-name malyon-keymap-read 'malyon-keymap-read)
  (setq malyon-history-saved-up   (global-key-binding [up]))
  (setq malyon-history-saved-down (global-key-binding [down]))
  (define-key malyon-keymap-read "\r"        'malyon-end-input)
  (define-key malyon-keymap-read [up]        'malyon-history-previous-char)
  (define-key malyon-keymap-read [down]      'malyon-history-next-char)
  (define-key malyon-keymap-read "\M-p"      'malyon-history-previous-char)
  (define-key malyon-keymap-read "\M-n"      'malyon-history-next-char)
  (define-key malyon-keymap-read "\C-a"      'malyon-beginning-of-line)
  (define-key malyon-keymap-read "\C-w"      'malyon-kill-region)
  (define-key malyon-keymap-read "\C-k"      'malyon-kill-line)
  (define-key malyon-keymap-read "\M-d"      'malyon-kill-word)
  (define-key malyon-keymap-read "\C-y"      'malyon-yank)
  (define-key malyon-keymap-read "\M-y"      'malyon-yank-pop)
  (define-key malyon-keymap-read "\C-d"      'malyon-delete-char)
  (define-key malyon-keymap-read "\d"        'malyon-backward-delete-char)
  (define-key malyon-keymap-read [del]       'malyon-delete-char)
  (define-key malyon-keymap-read [backspace] 'malyon-backward-delete-char)
  (substitute-key-definition (lookup-key (current-global-map) "a")
           'malyon-self-insert-command
           malyon-keymap-read (current-global-map)))

(defvar malyon-keymap-readchar nil
  "Keymap for malyon mode for waiting for input.")

(if malyon-keymap-readchar
    '()
  (setq malyon-keymap-readchar (make-sparse-keymap))
  (malyon-set-keymap-name malyon-keymap-readchar 'malyon-keymap-readchar)
  (define-key malyon-keymap-readchar "\r" 'malyon-wait-char)
  (substitute-key-definition (lookup-key (current-global-map) "a")
           'malyon-wait-char
           malyon-keymap-readchar (current-global-map)))

(defvar malyon-keymap-more nil
  "Keymap for malyon mode for browsing through text.")

(if malyon-keymap-more
    '()
  (setq malyon-keymap-more (make-sparse-keymap))
  (malyon-set-keymap-name malyon-keymap-more 'malyon-keymap-more)
  (define-key malyon-keymap-more "\r" 'malyon-more-char)
  (substitute-key-definition (lookup-key (current-global-map) "a")
           'malyon-more-char
           malyon-keymap-more (current-global-map)))

(defvar malyon-keymap-more-status nil
  "Keymap for malyon mode for browsing through the status buffer.")

(if malyon-keymap-more-status
    '()
  (setq malyon-keymap-more-status (make-sparse-keymap))
  (malyon-set-keymap-name malyon-keymap-more-status 'malyon-keymap-more-status)
  (define-key malyon-keymap-more-status "\r" 'malyon-more-char-status)
  (substitute-key-definition (lookup-key (current-global-map) "a")
           'malyon-more-char-status
           malyon-keymap-more-status (current-global-map)))

(defvar malyon-faces nil
  "An association list of text faces used by the malyon mode.")

(defun malyon-initialize-faces ()
  (copy-face 'default  'malyon-face-plain)
  (copy-face 'bold     'malyon-face-reverse)
  (copy-face 'bold     'malyon-face-bold)
  (copy-face 'italic   'malyon-face-italic)
  (copy-face 'default  'malyon-face-error)
  (set-face-foreground 'malyon-face-error "red")
  (setq malyon-faces '((0 . malyon-face-plain)
           (1 . malyon-face-reverse)
           (2 . malyon-face-bold)
           (4 . malyon-face-italic)
           (8 . malyon-face-plain))))

(defvar malyon-print-separator nil
  "A flag indicating whether to print the * * * separator.")

(defun malyon-begin-section ()
  "Print a section divider and begin a new section."
  (if malyon-print-separator
      (progn
  (malyon-mapc 'malyon-putchar-transcript '(?\n ?\n ?* ?  ?* ?  ?*))
  (center-line)
  (malyon-mapc 'malyon-putchar-transcript '(?\n ?\n))
  (setq malyon-print-separator nil)))
  (narrow-to-region (point-max) (point-max)))

(if malyon-whitespace
    '()
  (setq malyon-whitespace (list (malyon-char-to-int ? )
        (malyon-char-to-int ?\t)
        (malyon-char-to-int ?\n)
        (malyon-char-to-int ?\r))))

;; memory utilities

(defsubst malyon-read-byte (address)
  "Read a byte at address in the story file."
  (if (<= 0 address)
      (aref malyon-story-file address)
    (aref malyon-story-file (+ 65536 address))))

(defsubst malyon-store-byte (address value)
  "Store a byte at address in the story file."
  (if (<= 0 address)
      (aset malyon-story-file address (logand 255 value))
    (aset malyon-story-file (+ 65536 address) (logand 255 value))))

(defsubst malyon-read-word (address)
  "Read a word at address in the story file."
  (if (<= 0 address)
      (logior (lsh (aref malyon-story-file address) 8)
        (aref malyon-story-file (+ 1 address)))
    (logior (lsh (aref malyon-story-file (+ 65536 address)) 8)
      (aref malyon-story-file (+ 65537 address)))))
  
(defsubst malyon-store-word (address value)
  "Store a word at address in the story file."
  (if (<= 0 address)
      (progn
  (aset malyon-story-file address (logand 255 (lsh value -8)))
  (aset malyon-story-file (+ 1 address) (logand 255 value)))
    (aset malyon-story-file (+ 65536 address) (logand 255 (lsh value -8)))
    (aset malyon-story-file (+ 65537 address) (logand 255 value))))

(defsubst malyon-read-code-byte ()
  "Read the next byte at the program counter location."
  (setq malyon-instruction-pointer (+ malyon-instruction-pointer 1))
  (malyon-read-byte (- malyon-instruction-pointer 1)))

(defsubst malyon-read-code-word ()
  "Read the next word at the program counter location."
  (setq malyon-instruction-pointer (+ malyon-instruction-pointer 2))
  (malyon-read-word (- malyon-instruction-pointer 2)))

(defsubst malyon-pop-stack ()
  "Pop a value off the stack."
  (if (> 0 malyon-stack-pointer)
      (malyon-fatal-error "stack underflow."))
  (setq malyon-stack-pointer (- malyon-stack-pointer 1))
  (aref malyon-stack (+ malyon-stack-pointer 1)))

(defsubst malyon-read-local-variable (variable)
  "Read a local variable."
  (aref malyon-stack (+ variable malyon-frame-pointer)))

(defsubst malyon-read-global-variable (variable)
  "Read a global variable."
  (malyon-read-word (+ malyon-global-variables (* 2 variable))))

(defsubst malyon-read-variable (variable)
  "Read a variable."
  (cond ((= variable 0)  (malyon-pop-stack))
  ((< variable 16) (malyon-read-local-variable variable))
  (t               (malyon-read-global-variable (- variable 16)))))

(defsubst malyon-push-stack (value)
  "Push a value onto the stack."
  (setq malyon-stack-pointer (+ malyon-stack-pointer 1))
  (aset malyon-stack malyon-stack-pointer value))

(defsubst malyon-store-local-variable (variable value)
  "Store a value in a local variable."
  (aset malyon-stack (+ variable malyon-frame-pointer) value))

(defsubst malyon-store-global-variable (variable value)
  "Store a value in a global variable."
  (malyon-store-word (+ malyon-global-variables (* 2 variable)) value))

(defsubst malyon-store-variable (var value)
  "Store the value in a variable."
  (setq value (logand 65535 value))
  (cond ((= var 0)  (malyon-push-stack value))
  ((< var 16) (malyon-store-local-variable var value))
  (t          (malyon-store-global-variable (- var 16) value))))

;; list of opcodes

(defvar malyon-opcodes
  [malyon-opcode-nop
   malyon-opcode-je            malyon-opcode-jl
   malyon-opcode-jg              malyon-opcode-dec-chk
   malyon-opcode-inc-chk         malyon-opcode-jin
   malyon-opcode-test            malyon-opcode-or
   malyon-opcode-and             malyon-opcode-test-attr
   malyon-opcode-set-attr        malyon-opcode-clear-attr
   malyon-opcode-store           malyon-opcode-insert-obj
   malyon-opcode-loadw           malyon-opcode-loadb
   malyon-opcode-get-prop        malyon-opcode-get-prop-addr
   malyon-opcode-get-next-prop   malyon-opcode-add
   malyon-opcode-sub             malyon-opcode-mul
   malyon-opcode-div             malyon-opcode-mod
   malyon-opcode-calls           malyon-opcode-calln
   malyon-opcode-set-color       malyon-opcode-throw
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-je              malyon-opcode-jl
   malyon-opcode-jg              malyon-opcode-dec-chk
   malyon-opcode-inc-chk         malyon-opcode-jin
   malyon-opcode-test            malyon-opcode-or
   malyon-opcode-and             malyon-opcode-test-attr
   malyon-opcode-set-attr        malyon-opcode-clear-attr
   malyon-opcode-store           malyon-opcode-insert-obj
   malyon-opcode-loadw           malyon-opcode-loadb
   malyon-opcode-get-prop        malyon-opcode-get-prop-addr
   malyon-opcode-get-next-prop   malyon-opcode-add
   malyon-opcode-sub             malyon-opcode-mul
   malyon-opcode-div             malyon-opcode-mod
   malyon-opcode-calls           malyon-opcode-calln
   malyon-opcode-set-color       malyon-opcode-throw
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-je              malyon-opcode-jl 
   malyon-opcode-jg              malyon-opcode-dec-chk
   malyon-opcode-inc-chk         malyon-opcode-jin
   malyon-opcode-test            malyon-opcode-or
   malyon-opcode-and             malyon-opcode-test-attr
   malyon-opcode-set-attr        malyon-opcode-clear-attr
   malyon-opcode-store           malyon-opcode-insert-obj
   malyon-opcode-loadw           malyon-opcode-loadb
   malyon-opcode-get-prop        malyon-opcode-get-prop-addr
   malyon-opcode-get-next-prop   malyon-opcode-add
   malyon-opcode-sub             malyon-opcode-mul
   malyon-opcode-div             malyon-opcode-mod
   malyon-opcode-calls           malyon-opcode-calln
   malyon-opcode-set-color       malyon-opcode-throw
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-je              malyon-opcode-jl
   malyon-opcode-jg              malyon-opcode-dec-chk
   malyon-opcode-inc-chk         malyon-opcode-jin
   malyon-opcode-test            malyon-opcode-or
   malyon-opcode-and             malyon-opcode-test-attr
   malyon-opcode-set-attr        malyon-opcode-clear-attr
   malyon-opcode-store           malyon-opcode-insert-obj
   malyon-opcode-loadw           malyon-opcode-loadb
   malyon-opcode-get-prop        malyon-opcode-get-prop-addr
   malyon-opcode-get-next-prop   malyon-opcode-add
   malyon-opcode-sub             malyon-opcode-mul
   malyon-opcode-div             malyon-opcode-mod
   malyon-opcode-calls           malyon-opcode-calln
   malyon-opcode-set-color       malyon-opcode-throw
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop             malyon-opcode-jz
   malyon-opcode-get-sibling     malyon-opcode-get-child
   malyon-opcode-get-parent      malyon-opcode-get-prop-len
   malyon-opcode-inc             malyon-opcode-dec
   malyon-opcode-print-addr      malyon-opcode-calls
   malyon-opcode-remove-obj      malyon-opcode-print-obj
   malyon-opcode-ret             malyon-opcode-jump
   malyon-opcode-print-paddr     malyon-opcode-load
   malyon-opcode-calln           malyon-opcode-jz
   malyon-opcode-get-sibling     malyon-opcode-get-child
   malyon-opcode-get-parent      malyon-opcode-get-prop-len
   malyon-opcode-inc             malyon-opcode-dec
   malyon-opcode-print-addr      malyon-opcode-calls
   malyon-opcode-remove-obj      malyon-opcode-print-obj
   malyon-opcode-ret             malyon-opcode-jump
   malyon-opcode-print-paddr     malyon-opcode-load
   malyon-opcode-calln           malyon-opcode-jz
   malyon-opcode-get-sibling     malyon-opcode-get-child
   malyon-opcode-get-parent      malyon-opcode-get-prop-len
   malyon-opcode-inc             malyon-opcode-dec
   malyon-opcode-print-addr      malyon-opcode-calls
   malyon-opcode-remove-obj      malyon-opcode-print-obj
   malyon-opcode-ret             malyon-opcode-jump
   malyon-opcode-print-paddr     malyon-opcode-load
   malyon-opcode-calln           malyon-opcode-rtrue
   malyon-opcode-rfalse          malyon-opcode-print
   malyon-opcode-print-ret       malyon-opcode-nop
   malyon-opcode-illegal         malyon-opcode-illegal
   malyon-opcode-restart         malyon-opcode-ret-popped
   malyon-opcode-catch           malyon-opcode-quit
   malyon-opcode-new-line        malyon-opcode-illegal
   malyon-opcode-verify          malyon-opcode-illegal
   malyon-opcode-piracy          malyon-opcode-nop
   malyon-opcode-je              malyon-opcode-jl
   malyon-opcode-jg              malyon-opcode-dec-chk
   malyon-opcode-inc-chk         malyon-opcode-jin
   malyon-opcode-test            malyon-opcode-or
   malyon-opcode-and             malyon-opcode-test-attr
   malyon-opcode-set-attr        malyon-opcode-clear-attr
   malyon-opcode-store           malyon-opcode-insert-obj
   malyon-opcode-loadw           malyon-opcode-loadb
   malyon-opcode-get-prop        malyon-opcode-get-prop-addr
   malyon-opcode-get-next-prop   malyon-opcode-add
   malyon-opcode-sub             malyon-opcode-mul
   malyon-opcode-div             malyon-opcode-mod
   malyon-opcode-calls           malyon-opcode-calln
   malyon-opcode-set-color       malyon-opcode-throw
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop             malyon-opcode-calls
   malyon-opcode-storew          malyon-opcode-storeb
   malyon-opcode-put-prop        malyon-opcode-aread
   malyon-opcode-print-char      malyon-opcode-print-num
   malyon-opcode-random          malyon-opcode-push
   malyon-opcode-pull            malyon-opcode-split-window
   malyon-opcode-set-window      malyon-opcode-calls
   malyon-opcode-erase-window    malyon-opcode-erase-line
   malyon-opcode-set-cursor      malyon-opcode-get-cursor
   malyon-opcode-set-text-style  malyon-opcode-buffer-mode
   malyon-opcode-output-stream   malyon-opcode-input-stream
   malyon-opcode-nop             malyon-opcode-read-char
   malyon-opcode-scan-table      malyon-opcode-not
   malyon-opcode-calln           malyon-opcode-calln
   malyon-opcode-tokenise        malyon-opcode-encode-text
   malyon-opcode-copy-table      malyon-opcode-print-table
   malyon-opcode-check-arg-count malyon-opcode-save
   malyon-opcode-restore         malyon-opcode-log-shift
   malyon-opcode-art-shift       malyon-opcode-set-font
   malyon-opcode-illegal         malyon-opcode-illegal
   malyon-opcode-illegal         malyon-opcode-illegal
   malyon-opcode-save-undo       malyon-opcode-restore-undo
   malyon-opcode-print-unicode   malyon-opcode-check-unicode
   malyon-opcode-nop             malyon-opcode-nop
   malyon-opcode-nop]
  "A vector of all known legal z code opcodes.")

;; initialization

(defun malyon-load-story-file (file-name)
  "Load a z code story file into an internal vector."
  (save-excursion
    (set-buffer (create-file-buffer file-name))
    (malyon-disable-multibyte)
    (malyon-erase-buffer)
    (let ((coding-system-for-read 'binary))
      (insert-file-contents file-name))
    (setq malyon-story-file-name file-name)
    (setq malyon-story-file (buffer-substring-no-properties (point-min)
                  (point-max)))
    (setq malyon-story-file (malyon-string-to-vector malyon-story-file))
    (if (not (eq ?\^A 1))
  (let ((i 0))
    (while (< i (length malyon-story-file))
      (aset malyon-story-file
      i
      (malyon-char-to-int (aref malyon-story-file i)))
      (setq i (+ 1 i)))))
    (kill-buffer nil)))

(defun malyon-initialize ()
  "Initialize the z code interpreter."
;  (malyon-trace-file)
  (setq malyon-game-state-quetzal t)
  (malyon-initialize-faces)
  (malyon-initialize-status)
  (malyon-initialize-transcript)
  (malyon-initialize-windows)
  (malyon-initialize-story-header)
  (malyon-initialize-registers)
  (malyon-initialize-opcodes)
  (malyon-history-clear)
  (setq malyon-game-state-restart (malyon-current-game-state))
  (malyon-print-header))

(defun malyon-initialize-status ()
  "Initialize the status buffer."
  (setq malyon-status-buffer (get-buffer-create "Malyon Status"))
  (switch-to-buffer malyon-status-buffer)
  (malyon-erase-buffer)
  (kill-all-local-variables)
  (setq malyon-status-buffer-point (point))
  (setq malyon-status-buffer-lines 0)
  (setq malyon-status-buffer-delayed-split nil)
  (use-local-map malyon-keymap-read)
  (set-syntax-table malyon-syntax-table)
  (setq mode-name "Malyon")
  (setq major-mode 'malyon-mode)
  (run-hooks 'malyon-mode-hook))

(defun malyon-initialize-transcript ()
  "Initialize the transcript buffer."
  (setq malyon-transcript-buffer (get-buffer-create "Malyon Transcript"))
  (switch-to-buffer malyon-transcript-buffer)
  (malyon-erase-buffer)
  (kill-all-local-variables)
  (setq malyon-last-cursor-position-after-input
  (malyon-point-max malyon-transcript-buffer))
  (use-local-map malyon-keymap-read)
  (set-syntax-table malyon-syntax-table)
  (setq fill-column malyon-max-column)
  (auto-fill-mode 1)
  (setq mode-name "Malyon")
  (setq major-mode 'malyon-mode)
  (run-hooks 'malyon-mode-hook))

(defun malyon-initialize-windows ()
  "Initialize the window configuration for the z machine."
  (setq window-min-height 3)
  (setq malyon-transcript-buffer-buffered t)
  (malyon-set-window-configuration 0)
  (malyon-opcode-set-window 0))

(defun malyon-initialize-story-header ()
  "Initializes the header section of the story file."
  (malyon-store-byte 1  
         (if (>= malyon-story-version 5)
       28
           (logior 48 (malyon-read-byte 1))))
  (malyon-store-byte 16 (logand 440 (malyon-read-byte 16)))
  (malyon-store-byte 30 1)
  (malyon-store-byte 31 65)
  (malyon-store-byte 32 255)
  (malyon-store-byte 33 (- malyon-max-column 1))
  (malyon-store-word 34 (- malyon-max-column 1))
  (malyon-store-word 36 255)
  (malyon-store-word 38 1)
  (malyon-store-word 39 1)
  (malyon-store-byte 44 0)
  (malyon-store-byte 45 0)
  (malyon-store-byte 50 1)
  (malyon-store-byte 51 0))

(defun malyon-initialize-registers ()
  "Initialize the interpreter's internal registers."
  (setq malyon-stack (make-vector 1024 0))
  (setq malyon-stack-pointer -1)
  (malyon-push-initial-frame)
  (setq malyon-frame-pointer malyon-stack-pointer)
  (setq malyon-instruction-pointer (malyon-read-word 6))
  (setq malyon-global-variables (malyon-read-word 12))
  (setq malyon-object-table (malyon-read-word 10))
  (cond ((< malyon-story-version 5)
   (setq malyon-object-table-entry-size 9)
   (setq malyon-object-properties       31)
   (setq malyon-object-property-offset  7))
  (t
   (setq malyon-object-table-entry-size 14)
   (setq malyon-object-properties       63)
   (setq malyon-object-property-offset  12)))
  (setq malyon-abbreviations (malyon-read-word 24))
  (if (< malyon-story-version 5)
      (setq malyon-score-game (zerop (logand 2 (malyon-read-byte 1)))))
  (setq malyon-packed-multiplier
  (malyon-cadr (assq malyon-story-version '((3 2) (5 4) (8 8)))))
  (if (or (< malyon-story-version 5) (zerop (malyon-read-word 52)))
      (setq malyon-alphabet (concat "abcdefghijklmnopqrstuvwxyz"
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
            " \n0123456789.,!?_#'\"/\\-:()"))
    (setq malyon-alphabet (make-string 78 ? ))
    (let ((i 0))
      (while (< i 78)
  (aset malyon-alphabet i
        (malyon-read-byte (+ i (malyon-read-word 52))))
  (setq i (+ 1 i)))))
  (malyon-initialize-unicode-table)
  (setq malyon-dictionary (malyon-read-word 8))
  (setq malyon-dictionary-entry-length
  (malyon-read-byte
   (+ 1 malyon-dictionary (malyon-read-byte malyon-dictionary))))
  (setq malyon-dictionary-num-entries
  (malyon-read-word
   (+ 2 malyon-dictionary (malyon-read-byte malyon-dictionary))))
  (setq malyon-dictionary-entries
  (+ 4 malyon-dictionary (malyon-read-byte malyon-dictionary)))
  (setq malyon-dictionary-word-length (if (< malyon-story-version 5) 3 5))
  (setq malyon-current-face 'malyon-face-plain)
  (setq malyon-print-separator nil)
  (malyon-initialize-output-streams))

(defun malyon-initialize-opcodes ()
  "Initialize the opcode table used by the story file."
  (cond ((< malyon-story-version 5)
   (aset malyon-opcodes 143 'malyon-opcode-not)
   (aset malyon-opcodes 181 'malyon-opcode-save)
   (aset malyon-opcodes 182 'malyon-opcode-restore)
   (aset malyon-opcodes 185 'malyon-opcode-pop)
   (aset malyon-opcodes 188 'malyon-opcode-show-status))
  (t
   (aset malyon-opcodes 143 'malyon-opcode-calln)
   (aset malyon-opcodes 181 'malyon-opcode-illegal)
   (aset malyon-opcodes 182 'malyon-opcode-illegal)
     (aset malyon-opcodes 185 'malyon-opcode-catch)
   (aset malyon-opcodes 188 'malyon-opcode-illegal))))

(defun malyon-print-header ()
  "Print malyon mode header information."
  (malyon-opcode-set-text-style 2)
  (malyon-print "Malyon V 1.0.3")
  (malyon-opcode-set-text-style 0)
  (malyon-newline)
  (malyon-print "A z-code interpreter for version 3, 5, and 8 games.")
  (malyon-newline)
  (malyon-print "(c) 1999-2011 by Peter Ilberg <peter.ilberg@gmail.com>")
  (malyon-newline)
  (malyon-newline))

;; cleanup

(defun malyon-cleanup ()
  "Clean up the z code interpreter."
  (condition-case nil
      (progn
  (setq malyon-story-file nil)
  (setq malyon-window-configuration nil)
  (setq malyon-game-state-restart nil)
  (setq malyon-game-state-undo nil)
  (if (get-buffer "Malyon Status")
      (kill-buffer (get-buffer "Malyon Status")))
  (if (get-buffer "Malyon Transcript")
      (progn
        (switch-to-buffer (get-buffer "Malyon Transcript"))
        (malyon-redisplay-frame (selected-frame) t)
        (delete-other-windows (get-buffer-window (current-buffer)))
        (widen)
        (text-mode)))
  (setq malyon-status-buffer nil)
  (setq malyon-transcript-buffer nil))
    (error
     (malyon-fatal-error "cleanup failed."))))

;; error handling

(defun malyon-fatal-error (message)
  "Print error message and abort."
  (setq message (concat "Malyon fatal error: " message))
  (unwind-protect
      (save-excursion
  (set-buffer malyon-transcript-buffer)
  (goto-char (point-max))
  (newline)
  (newline)
  (put-text-property 0
         (length message)
         'face
         'malyon-face-error
         message)
  (insert message)
  (newline))
    (malyon-cleanup)
    (malyon-redisplay-frame (selected-frame) t)
    (error message)))

;; conversion of zscii to ascii

(defvar malyon-unicode-table nil
  "An array mapping zscii characters to latin-1 ones.")

(defvar malyon-default-unicode-table nil
  "The default array mapping zscii characters to latin-1 ones.")

(if malyon-default-unicode-table
    '()
  (setq malyon-default-unicode-table
  [32 
   0   0   0   0   0   0   0       ;   1 -   7
   8   0   0   0   0   10  0   0   ;   8 -  15
   0   0   0   0   0   0   0   0   ;  16 -  23
   0   0   0   39  0   0   0   0   ;  24 -  31
   32  33  34  35  36  37  38  39  ;  32 -  39
   40  41  42  43  44  45  46  47  ;  40 -  47
   48  49  50  51  52  53  54  55  ;  48 -  55
   56  57  58  59  60  61  62  63  ;  56 -  63
   64  65  66  67  68  69  70  71  ;  64 -  71
   72  73  74  75  76  77  78  79  ;  72 -  79
   80  81  82  83  84  85  86  87  ;  80 -  87
   88  89  90  91  92  93  94  95  ;  88 -  95
   96  97  98  99  100 101 102 103 ;  96 - 103
   104 105 106 107 108 109 110 111 ; 104 - 111
   112 113 114 115 116 117 118 119 ; 112 - 119
   120 121 122 123 124 125 126 0   ; 120 - 127
   0   0   0   0   0   0   0   0   ; 128 - 135
   0   0   0   0   0   0   0   0   ; 136 - 143
   0   48  49  50  51  52  53  54  ; 144 - 151
   55  56  57  228 246 252 196 214 ; 152 - 159
   220 223 187 171 235 239 255 203 ; 160 - 167
   207 225 233 237 243 250 253 193 ; 168 - 175
   201 205 211 218 221 224 232 236 ; 176 - 183
   242 249 192 200 204 210 217 226 ; 184 - 191
   234 238 244 251 194 202 206 212 ; 192 - 199
   219 229 197 248 216 227 241 245 ; 200 - 207
   195 209 213 230 198 231 199 254 ; 208 - 215
   240 222 208 163 63  63  161 191 ; 216 - 223
   0   0   0   0   0   0   0   0   ; 224 - 231
   0   0   0   0   0   0   0   0   ; 232 - 239
   0   0   0   0   0   0   0   0   ; 240 - 247
   0   0   0   0   0   0   0   0   ; 248 - 255
   ]))

(defun malyon-initialize-unicode-table ()
  "Initializes the zscii-to-unicode conversion table."
  (setq malyon-unicode-table
  (copy-sequence malyon-default-unicode-table))
  (let* ((ext   (malyon-read-word 54))
   (len   (if (zerop ext) 0 (malyon-read-word ext)))
   (table (if (< len 3)   0 (malyon-read-word (+ ext 6)))))
    (if (or (< malyon-story-version 5) (zerop table))
  '()
      (let ((i 0))
  (while (< i 96)
    (aset malyon-unicode-table (+ 155 i) (malyon-char-to-int ??))
    (setq i (+ 1 i))))
      (setq len (malyon-read-byte table))
      (let ((i 0))
  (while (< i len)
    (aset malyon-unicode-table (+ 155 i)
    (malyon-read-word (+ table 1 i)))
    (setq i (+ 1 i)))))))

(defsubst malyon-zscii-to-unicode (char)
  "Converts a zscii character to unicode."
  (if (or (< char 0) (> char 255))
      ??
    (let ((uni (aref malyon-unicode-table char)))
      (if (zerop uni)
    ??
  (malyon-unibyte-char-to-multibyte (malyon-int-to-char uni))))))

(defsubst malyon-unicode-to-zscii (char)
  "Converts a unicode character to zscii."
  (setq char (malyon-multibyte-char-to-unibyte char))
  (setq char (if (malyon-characterp char) (malyon-char-to-int char) char))
  (if (= 13 char)
      ?\r
    (let ((i 1) (found 0))
      (while (and (< i 255) (zerop found))
  (if (= char (aref malyon-unicode-table i))
      (setq found i))
  (setq i (+ i 1)))
      (malyon-int-to-char found))))

;; output streams

(defvar malyon-output-streams nil
  "Valid output streams for the interpreter.")

(defvar malyon-output-streams-tables nil
  "A list of active tables for stream 3.")

(defun malyon-initialize-output-streams ()
  "Initializes the output streams."
  (setq malyon-output-streams '())
  (setq malyon-output-streams-tables '())
  (malyon-add-output-stream 1 0))

(defun malyon-output-stream-function (stream)
  "Returns the output function representing the given stream."
  (cond ((= 1 stream) (if (zerop malyon-current-window)
        'malyon-putchar-transcript
      'malyon-putchar-status))
  ((= 2 stream) 'malyon-putchar-printer)))

(defun malyon-add-output-stream (stream table)
  "Add a new output stream."
  (if (= stream 3)
      (progn
  (setq malyon-output-streams-tables
        (cons table malyon-output-streams-tables))
  (malyon-store-word table 0))
    (let ((function (malyon-output-stream-function stream)))
      (setq malyon-output-streams
      (if (member function malyon-output-streams)
    malyon-output-streams
        (cons function malyon-output-streams))))))

(defun malyon-remove-output-stream (stream)
  "Remove an output stream."
  (if (= stream 3)
      (setq malyon-output-streams-tables (cdr malyon-output-streams-tables))
    (setq malyon-output-streams
    (malyon-remove (malyon-output-stream-function stream)
       malyon-output-streams))))

(defun malyon-update-output-streams ()
  "Update output streams when the output window has changed."
  (let ((one (or (member 'malyon-putchar-transcript malyon-output-streams)
     (member 'malyon-putchar-status     malyon-output-streams))))
    (setq malyon-output-streams
    (malyon-remove 'malyon-putchar-transcript
       (malyon-remove 'malyon-putchar-status
          malyon-output-streams)))
    (if one
  (malyon-add-output-stream 1 0))))

(defsubst malyon-output-character (char)
  "Output a single character on all active streams."
  (setq char (malyon-zscii-to-unicode char))
  (if malyon-output-streams-tables
      (malyon-putchar-table char (car malyon-output-streams-tables))
    (malyon-mapc (lambda (s) (funcall s char)) malyon-output-streams)))

;; printing text

(defsubst malyon-abbrev (abbrev x)
  "Print an abbreviation."
  (malyon-print-ztext
   (* 2 (malyon-read-word (+ malyon-abbreviations
           (* 2 (+ x (* 32 (1- abbrev)))))))))

(defun malyon-newline ()
  "Print a newline."
  (if (eq malyon-status-buffer (current-buffer))
      (goto-char malyon-status-buffer-point)
    (goto-char (point-max)))
  (malyon-output-character ?\r)
  (if (eq malyon-status-buffer (current-buffer))
      (setq malyon-status-buffer-point (point))
    (goto-char malyon-last-cursor-position-after-input))
  (malyon-redisplay-frame (selected-frame) nil))

(defun malyon-print (object)
  "Print text." 
  (let ((text (if (malyon-characterp object) (char-to-string object) object))
  (start))
    (if (eq malyon-transcript-buffer (current-buffer))
  (goto-char (point-max))
      (goto-char malyon-status-buffer-point))
    (setq start (point))
    (malyon-print-characters (malyon-string-to-list text))
    (put-text-property start (point) 'face malyon-current-face)
    (if (eq malyon-status-buffer (current-buffer))
  (setq malyon-status-buffer-point (point))
      (goto-char malyon-last-cursor-position-after-input))))

(defun malyon-print-characters (text)
  "Print a list of characters."
  (malyon-mapc 'malyon-output-character text))

(defsubst malyon-print-state-new (char shift abbr zscii zcode)
  "Generate a new print state."
  (list char shift abbr zscii zcode))

(defsubst malyon-print-state-initial ()
  "Returns an initial state for the ztext decoder."
  (malyon-print-state-new nil -6 0 0 0))

(defsubst malyon-print-state-next (x ignore shift abbr zscii z)
  "Print state transition function."
  (cond ((= zscii 2)
   (malyon-print-state-new (+ z x) -6 0 0 0))
  ((= zscii 1)
   (malyon-print-state-new nil     -6 0 2 (* 32 x)))
  ((> abbr 0)
   (malyon-abbrev abbr x)
   (malyon-print-state-initial))
  ((= x 0)
   (malyon-print-state-new ?       -6 0 0 0))
  ((< x 4)
   (malyon-print-state-new nil     -6 x 0 0))
  ((= x 4)
   (malyon-print-state-new nil     20 0 0 0))
  ((= x 5)
   (malyon-print-state-new nil     46 0 0 0))
  ((and (= shift 46) (= x 6))
   (malyon-print-state-new nil     -6 0 1 0))
  ((and (= shift 46) (= x 7))
   (malyon-print-state-new ?\r     -6 0 0 0))
  (t
   (malyon-print-state-new
    (aref malyon-alphabet (+ shift x)) -6 0 0 0))))

(defun malyon-print-text (address)
  "Print text at address and return the address of the following byte."
  (let ((start))
    (if (eq malyon-transcript-buffer (current-buffer))
  (goto-char (point-max))
      (goto-char malyon-status-buffer-point))
    (setq start (point))
    (setq address (malyon-print-ztext address))
    (put-text-property start (point) 'face malyon-current-face)
    (if (eq malyon-status-buffer (current-buffer))
  (setq malyon-status-buffer-point (point))
      (goto-char malyon-last-cursor-position-after-input))
    (malyon-redisplay-frame (selected-frame) nil)
    address))

(defun malyon-print-ztext (address)
  "Print the ztext stored at the given address."
  (let ((high 0) (low) (a) (b) (c) (state (malyon-print-state-initial)))
    (while (zerop (logand 128 high))
      (setq high (malyon-read-byte address))
      (setq low  (malyon-read-byte (+ 1 address)))
      (setq a    (logand 31 (lsh high -2)))
      (setq b    (logand 31 (logior (lsh high 3) (lsh low -5))))
      (setq c    (logand 31 low))
      (setq state (apply 'malyon-print-state-next a state))
      (if (car state) (malyon-output-character (car state)))
      (setq state (apply 'malyon-print-state-next b state))
      (if (car state) (malyon-output-character (car state)))
      (setq state (apply 'malyon-print-state-next c state))
      (if (car state) (malyon-output-character (car state)))
      (setq address (+ 2 address)))
    address))

(defun malyon-putchar-transcript (char)
  "Print a single character in the transcript window."
  (if (char-equal char ?\n)
      (newline 1)
    (insert char)
    (setq malyon-print-separator (null (member char malyon-whitespace))))
  (if (and malyon-transcript-buffer-buffered
     (> (current-column) (current-fill-column)))
      (progn
  (end-of-line)
  (forward-word -1)
  (if (< 0 (current-column))
      (newline 1))
  (end-of-line))))

(defun malyon-putchar-status (char)
  "Print a single character in the status window."
  (if malyon-status-buffer-delayed-split
      (progn
  (malyon-split-buffer-windows malyon-status-buffer-delayed-split)
  (other-window 1)))
  (if (char-equal char ?\n)
      (progn
  (beginning-of-line)
  (forward-line 1)
  (if (= (point) (point-max))
      (forward-line -1)))
    (if (> (current-column) (current-fill-column))
  '()
      (insert char)
      (delete-char 1))))

(defun malyon-putchar-table (char table)
  "Print a single character into a table."
  (setq char (malyon-unicode-to-zscii char))
  (malyon-store-byte (+ 2 table (malyon-read-word table)) char)
  (malyon-store-word table (+ 1 (malyon-read-word table))))

(defun malyon-putchar-printer (char)
  "Print a single character onto a printer."); not yet implemented

;; more

(defvar malyon-more-continue-keymap nil
  "The keymap with which to continue after More has finished.")

(defun malyon-more (keymap)
  "Enter More mode."
  (if (eq malyon-status-buffer (current-buffer))
      (use-local-map keymap)
    (if (< malyon-story-version 5) (malyon-opcode-show-status))
    (if (< (count-lines malyon-last-cursor-position-after-input (point-max))
     (malyon-window-displayed-height))
  (progn
    (malyon-adjust-transcript)
    (use-local-map keymap))
      (goto-char malyon-last-cursor-position-after-input)
      (beginning-of-line)
      (recenter 1)
      (setq malyon-more-continue-keymap keymap)
      (use-local-map malyon-keymap-more)
      (message "[More]"))))

(defun malyon-more-status-buffer ()
  "Enter More mode for the status buffer."
  (setq malyon-more-continue-keymap (current-local-map))
  (use-local-map malyon-keymap-more-status)
  (message "[More]")
  (throw 'malyon-end-of-interpreter-loop 'malyon-waiting-for-input))

;; input history

(defvar malyon-history nil
  "The input history.")

(defun malyon-history-previous ()
  "Move one entry up in the input history."
  (let ((prev (aref malyon-history 0))
  (curr (aref malyon-history 1))
  (next (aref malyon-history 2)))
    (if (null prev)
  curr
      (aset malyon-history 2 (if curr (cons curr next) next))
      (aset malyon-history 0 (cdr prev))
      (aset malyon-history 1 (car prev)))))

(defun malyon-history-next ()
  "Move one entry down in the input history."
  (let ((prev (aref malyon-history 0))
  (curr (aref malyon-history 1))
  (next (aref malyon-history 2)))
    (if (null next)
  curr
      (aset malyon-history 0 (if curr (cons curr prev) prev))
      (aset malyon-history 2 (cdr next))
      (aset malyon-history 1 (car next)))))

(defun malyon-history-clear ()
  "Clear the input history."
  (setq malyon-history (vector '() nil '())))

(defun malyon-history-insert (entry)
  "Insert an entry into the input history."
  (let* ((prev (aref malyon-history 0))
   (curr (aref malyon-history 1))
   (next (aref malyon-history 2))
   (l    (malyon-remove entry
            (append (nreverse prev)
              (if curr (cons curr next) next))))
   (cut  (- (length l) 19)))
    (while (> cut 0)
      (setq l   (cdr l)
      cut (- cut 1)))
    (aset malyon-history 0
    (malyon-remove nil (malyon-remove "" (cons entry (nreverse l)))))
    (aset malyon-history 1 nil)
    (aset malyon-history 2 '())))

;; dictionary lookup

(defun malyon-dictionary-word (chars)
  "Convert a list of characters into a dictionary word."
  (list (car (car chars))
  (length chars)
  (malyon-encode-dictionary-word (append (malyon-mapcan 'cdr chars)
                 '(5 5 5 5 5 5 5 5)))))

(defsubst malyon-join-characters (stop list)
  "Joins three ztext characters into two bytes."
  (let ((a (car          list))
  (b (malyon-cadr  list))
  (c (malyon-caddr list))
  (x (if (zerop stop) 0 128)))
    (list (logior x (logand 255 (logior (lsh a 2) (lsh b -3))))
    (logand 255 (logior (lsh b 5) c)))))

(defun malyon-encode-dictionary-word (l)
  "Converts a list of ztext characters into a dictionary word."
  (let* ((first  l)
   (second (malyon-cdddr first))
   (third  (malyon-cdddr second)))
    (apply 'vector
     (if (< malyon-story-version 5)
         (append (malyon-join-characters 0 first)
           (malyon-join-characters 1 second))
       (append (malyon-join-characters 0 first)
         (malyon-join-characters 0 second)
         (malyon-join-characters 1 third))))))

(defun malyon-lookup (dict code)
  "Look for the given code in the dictionary and return its address."
  (cond ((not code)                 0)
  ((not dict)                 (malyon-binary-search code))
  ((= dict malyon-dictionary) (malyon-binary-search code))
  (t                          (malyon-linear-search dict code))))

(defsubst malyon-compare-words (word address)
  "Compares the given word to the word stored at address."
  (let* ((i 0)
   (j address)
   (x (aref word i))
   (y (malyon-read-byte j)))
    (while (not (or (/= x y) (= i malyon-dictionary-word-length)))
      (setq i (+ 1 i)
      j (+ 1 j)
      x (aref word i)
      y (malyon-read-byte j)))
    (- x y)))

;; search functions

(defun malyon-binary-search (code)
  "Binary search through the main dictionary."
  (let* ((lower   0)
   (upper   (- malyon-dictionary-num-entries 1))
   (median  (/ (+ lower upper) 2))
   (entry   (+ malyon-dictionary-entries
         (* malyon-dictionary-entry-length median)))
   (looking (malyon-compare-words code entry)))
    (while (not (or (> lower upper) (zerop looking)))
      (setq lower   (if (< 0 looking) (+ median 1) lower)
            upper   (if (> 0 looking) (- median 1) upper)
      median  (/ (+ lower upper) 2)
      entry   (+ malyon-dictionary-entries
           (* malyon-dictionary-entry-length median))
      looking (malyon-compare-words code entry)))
    (if (zerop looking) entry 0)))

(defun malyon-linear-search (dictionary code)
  "Linear search through the given dictionary."
  (let* ((length  (malyon-read-byte (+ dictionary 1
               (malyon-read-byte dictionary))))
   (number  (malyon-read-word (+ dictionary 2
               (malyon-read-byte dictionary))))
   (entries (+ dictionary 4 (malyon-read-byte dictionary)))
   (i       0)
   (entry   (+ entries (* length i)))
   (looking (malyon-compare-words code entry)))
    (while (not (or (>= i number) (zerop looking)))
      (setq i       (+ 1 i)
      entry   (+ entries (* length i))
      looking (malyon-compare-words code entry)))
    (if (zerop looking) entry 0)))

;; encoding text and lexical analysis

(defun malyon-split-list (sep list &optional x)
  "Split a list into sublists as indicated by the separators."
  (cond ((null list)
   (list (nreverse x)))
  ((eq sep (car list))
   (cons (nreverse x) (malyon-split-list sep (cdr list) '())))
  (t
   (malyon-split-list sep (cdr list) (cons (car list) x)))))

(defun malyon-characters-to-words (list)
  "Turn the list of characters into a list of words."
  (mapcar 'malyon-dictionary-word
    (delete '() (malyon-split-list 'malyon-word-separator list))))

(defsubst malyon-char-in-string (c s)
  "Returns the index of c in s if found, or length of s."
  (let ((i 0))
    (while (not (or (= i (length s)) (= c (aref s i))))
      (setq i (+ 1 i)))
    i))

(defsubst malyon-encode-into-ztext (c)
  "Convert a character into ztext."
  (let* ((index (malyon-char-in-string c malyon-alphabet))
   (shift (floor index 26))
   (char  (+ 6 (mod index 26))))
    (cond ((> shift 2) (list 5 6 (logand 31 (lsh c -5)) (logand 31 c)))
    ((= shift 2) (list 5 char))
    ((= shift 1) (list 4 char))
    (t           (list char)))))

(defun malyon-encode-single-character (terminating-characters char)
  "Encode a character into ztext."
  (let ((pos (car char))
  (c   (cdr char)))
    (cond ((member c malyon-whitespace)
     (list 'malyon-word-separator))
    ((member c terminating-characters)
     (list 'malyon-word-separator
     (cons pos (malyon-encode-into-ztext c))
     'malyon-word-separator))
    (t (list (cons pos (malyon-encode-into-ztext c)))))))

(defun malyon-encode-character-list (dict list)
  "Encode the list of characters into ztext."
  (let ((l '())
  (i 0))
    (while (< i (malyon-read-byte dict))
      (setq l (cons (malyon-read-byte (+ dict 1 i)) l)
      i (+ 1 i)))
    (malyon-mapcan (lambda (x) (malyon-encode-single-character l x)) list)))

(defun malyon-text-length (address)
  "Return the length of the input text."
  (if (>= malyon-story-version 5)
      (malyon-read-byte (+ 1 address))
    (let ((i 0))
      (while (not (zerop (malyon-read-byte (+ i 1 address))))
  (setq i (+ i 1)))
      i)))

(defun malyon-text-to-character-list (address)
  "Convert the input text into a list of characters."
  (let ((i    (malyon-text-length address))
  (text '()))
    (while (< 0 i)
      (setq text (cons
      (cons (if (< malyon-story-version 5) i (+ 1 i))
      (malyon-read-byte
       (+ i address (if (< malyon-story-version 5) 0 1))))
      text)
      i    (- i 1)))
    text))

(defun malyon-text-to-words (address dictionary)
  "Turn ztext into a list of dictionary words."
  (malyon-characters-to-words
   (malyon-encode-character-list (if dictionary dictionary malyon-dictionary)
         (malyon-text-to-character-list address))))

;; window management

(defvar malyon-status-buffer-grew-this-turn nil
  "A flag signalling if the status buffer grew this turn.")

(defun malyon-adjust-transcript ()
  "Adjust the position of the transcript text."
  (save-excursion
    (setq malyon-status-buffer-grew-this-turn nil)
    (set-buffer malyon-transcript-buffer)
    (goto-char (point-max))
    (recenter (- (malyon-window-displayed-height) 2))))

(defun malyon-prepare-status-buffer (status)
  "Fill the status buffer with empty lines."
  (save-excursion
    (set-buffer malyon-status-buffer)
    (let ((lines (count-lines (point-min) (point-max)))
    (new   status))
      (if (zerop lines)
    (newline 1))
      (goto-char (point-max))
      (setq status (- status lines -1))
      (while (> status 0)
  (insert (make-string (+ 3 malyon-max-column) ? ))
  (newline 1)
  (setq status (- status 1)))
      (goto-char (point-min))
      (forward-line (+ 1 new))
      (kill-region (point) (point-max))
      (insert (make-string (+ 3 malyon-max-column) ? ))
      (newline 1))))

(defun malyon-restore-window-configuration ()
  "Restore the saved window configuration."
  (let ((buffer (window-buffer (selected-window))))
    (if malyon-window-configuration
  (set-window-configuration malyon-window-configuration))
    (cond ((eq malyon-status-buffer buffer)     (other-window 1))
    ((eq malyon-transcript-buffer buffer) (goto-char (point-max))))))

(defun malyon-set-window-configuration (status)
  "Set up the new window configuration."
  (cond ((< status malyon-status-buffer-lines)
   (setq malyon-status-buffer-delayed-split status)
   (if malyon-status-buffer-grew-this-turn
       (malyon-more-status-buffer)))
  ((> status malyon-status-buffer-lines)
   (malyon-split-buffer-windows status)
   (setq malyon-status-buffer-grew-this-turn t))
  ((not malyon-window-configuration)
   (malyon-split-buffer-windows status))))

(defun malyon-split-buffer-windows (status)
  "Split the buffer windows.
The status buffer gets 'status' lines while the transcript buffer
gets the remaining lines."
  (delete-other-windows (get-buffer-window (current-buffer)))
  (setq malyon-status-buffer-lines status)
  (setq malyon-status-buffer-delayed-split nil)
  (if (zerop status)
      '()
    (split-window (get-buffer-window (current-buffer)) (+ status 3))
    (switch-to-buffer malyon-status-buffer)
    (malyon-prepare-status-buffer status)
    (malyon-opcode-set-cursor 1 1)
    (other-window 1))
  (switch-to-buffer malyon-transcript-buffer)
  (setq malyon-window-configuration (current-window-configuration)))

;; getting and setting the machine state

(defun malyon-current-game-state ()
  "Return the current state of the interpreter."
  (vector malyon-instruction-pointer
    malyon-stack-pointer
          malyon-frame-pointer
    (copy-sequence malyon-stack)
    (copy-sequence malyon-story-file)
    malyon-game-state-quetzal))

(defun malyon-set-game-state (state)
  "Installs the given state as the new state of the interpreter."
  (setq malyon-instruction-pointer       (aref state 0))
  (setq malyon-stack-pointer             (aref state 1))
  (setq malyon-frame-pointer             (aref state 2))
  (setq malyon-stack (copy-sequence      (aref state 3)))
  (setq malyon-story-file (copy-sequence (aref state 4)))
  (setq malyon-game-state-quetzal        (aref state 5))
  (save-excursion
    (malyon-erase-buffer malyon-status-buffer)
    (malyon-split-buffer-windows 0)
    (setq malyon-last-cursor-position-after-input
    (malyon-point-max malyon-transcript-buffer))))

;; file utilities

(defsubst malyon-write-byte-to-file (byte)
  "Write a byte to a file."
  (insert-char (logand 255 byte) 1))

(defsubst malyon-write-word-to-file (word)
  "Write a word to the last opened file."
  (insert-char (logand 255 (lsh word -8)) 1)
  (insert-char (logand 255 word) 1))

(defsubst malyon-write-dword-to-file (dword)
  "Write a dword to the last opened file."
  (insert-char (logand 255 (lsh dword -24)) 1)
  (insert-char (logand 255 (lsh dword -16)) 1)
  (insert-char (logand 255 (lsh dword -8)) 1)
  (insert-char (logand 255 dword) 1))

(defsubst malyon-write-chunk-id-to-file (id)
  "Write a quetzal chunk id to the last opened file."
  (insert id))

(defsubst malyon-read-byte-from-file ()
  "Read the next byte from a file."
  (if (= (point) (point-max))
      0
    (forward-char 1)
    (malyon-char-to-int (malyon-char-before))))

(defsubst malyon-read-word-from-file ()
  "Read the next word from the last opened file."
  (logior (lsh (malyon-read-byte-from-file) 8) (malyon-read-byte-from-file)))

(defsubst malyon-read-dword-from-file ()
  "Read the next dword from the last opened file."
  (logior (lsh (malyon-read-byte-from-file) 24)
    (lsh (malyon-read-byte-from-file) 16)
    (lsh (malyon-read-byte-from-file) 8)
    (malyon-read-byte-from-file)))

(defsubst malyon-read-chunk-id-from-file ()
  "Read a quetzal chunk id from the last opened file."
  (string (malyon-int-to-char (malyon-read-byte-from-file))
    (malyon-int-to-char (malyon-read-byte-from-file))
    (malyon-int-to-char (malyon-read-byte-from-file))
    (malyon-int-to-char (malyon-read-byte-from-file))))

(defun malyon-get-file-name (address)
  "Retrieves the file name stored at address."
  (let ((name (make-string (malyon-read-byte address) ? ))
  (i    0))
    (while (< i (length name))
      (aset name i (malyon-read-byte (+ address 1 i)))
      (setq i (+ 1 i)))
    name))

;; saving data to disk

(defun malyon-save-file (file &optional table length)
  "Save the current game state or a memory section to disk."
  (interactive "FSave file: ")
  (condition-case nil
      (save-excursion
  (set-buffer (create-file-buffer file))
  (malyon-disable-multibyte)
  (malyon-erase-buffer)
  (cond (table (malyon-save-table table length))
        (malyon-game-state-quetzal
         (malyon-save-quetzal-state (malyon-current-game-state)))
        (t
         (malyon-save-game-state (malyon-current-game-state))))
  (let ((coding-system-for-write 'binary))
    (write-file file))
  (kill-buffer nil)
  1)
    (error 0)))

(defun malyon-save-table (table length)
  "Save the given section of memory to the file."
  (let ((i 0)
  (j table))
    (while (< i length)
      (malyon-write-byte-to-file (malyon-read-byte j))
      (setq i (+ 1 i)
      j (+ 1 j)))))

(defun malyon-save-game-state (state)
  "Saves the game state to disk."
  (let ((ip    (aref state 0))
  (sp    (aref state 1))
  (fp    (aref state 2))
  (stack (aref state 3))
  (mem   (aref state 4))
  (dyn   (malyon-read-word 14))
  (i     0))
    (malyon-write-word-to-file (length malyon-story-file-name))
    (while (< i (length malyon-story-file-name))
      (malyon-write-byte-to-file (aref malyon-story-file-name i))
      (setq i (+ 1 i)))
    (malyon-write-dword-to-file ip)
    (malyon-write-word-to-file  sp)
    (malyon-write-word-to-file  fp)
    (malyon-write-word-to-file  dyn)
    (setq i 0)
    (while (<= i sp)
      (malyon-write-dword-to-file (aref stack i))
      (setq i (+ 1 i)))
    (setq i 0)
    (while (< i dyn)
      (malyon-write-byte-to-file (aref mem i))
      (setq i (+ 1 i)))))

(defun malyon-save-quetzal-state (state)
  "Saves the game state to disk in quetzal format."
  (goto-char (point-min))
  (malyon-save-quetzal-ifhd state)
  (malyon-save-quetzal-cmem state)
  (malyon-save-quetzal-stks state)
  (goto-char (point-min))
  (malyon-write-chunk-id-to-file "IFZS")
  (goto-char (point-min))
  (malyon-write-dword-to-file (- (point-max) (point-min)))
  (goto-char (point-min))
  (malyon-write-chunk-id-to-file "FORM"))

(defun malyon-save-quetzal-ifhd (state)
  "Saves the IFhd chunk of the quetzal format."
  (malyon-write-chunk-id-to-file "IFhd")
  (malyon-write-dword-to-file 13)
  (malyon-write-word-to-file (malyon-read-word 2))
  (malyon-write-word-to-file (malyon-read-word 18))
  (malyon-write-word-to-file (malyon-read-word 20))
  (malyon-write-word-to-file (malyon-read-word 22))
  (malyon-write-word-to-file (malyon-read-word 28))
  (malyon-write-byte-to-file (lsh (aref state 0) -16))
  (malyon-write-byte-to-file (lsh (aref state 0) -8))
  (malyon-write-byte-to-file (aref state 0))
  (malyon-write-byte-to-file 0))

(defun malyon-save-quetzal-cmem (state)
  "Saves the CMem chunk of the quetzal format."
  (let ((beginning (point-max))
  (original  (aref malyon-game-state-restart 4))
  (current   (aref state 4))
  (size      (malyon-read-word 14))
  (byte      0)
  (count     0)
  (i         0))
    (goto-char (point-max))
    (while (< i size)
      (setq byte (logxor (aref current i) (aref original i)))
      (if (zerop byte)
    (setq count (+ 1 count))
  (while (> count 0)
    (malyon-write-byte-to-file 0)
    (setq count (- count 1))
    (malyon-write-byte-to-file (min 255 count))
    (setq count (- count (min 255 count))))
  (malyon-write-byte-to-file byte))
      (setq i (+ 1 i)))
    (setq size (- (point-max) beginning))
    (if (zerop (mod size 2)) '() (malyon-write-byte-to-file 0))
    (goto-char beginning)
    (malyon-write-chunk-id-to-file "CMem")
    (malyon-write-dword-to-file size)))

(defun malyon-save-quetzal-stks (state)
  "Saves the Stks chunk of the quetzal format."
  (let ((beginning (point-max))
  (size      0))
    (goto-char (point-max))
    (malyon-save-quetzal-stack-frame (- (aref state 2) 4)
             (aref state 1)
             (aref state 3))
    (setq size (- (point-max) beginning))
    (if (zerop (mod size 2)) '() (malyon-write-byte-to-file 0))
    (goto-char beginning)
    (malyon-write-chunk-id-to-file "Stks")
    (malyon-write-dword-to-file size)))

(defun malyon-save-quetzal-stack-frame (fp sp stack)
  "Saves the stack frames for the Stks chunk."
  (let* ((frame       (malyon-get-stack-frame fp sp stack))
   (frame-id    (aref frame 0))
   (previous-fp (aref frame 1))
   (previous-sp (aref frame 2))
   (return-addr (aref frame 3))
   (result-addr (aref frame 4))
   (local-vars  (aref frame 5))
   (num-args    (aref frame 6))
   (eval-stack  (aref frame 7)))
    (if (> frame-id 0)
  (malyon-save-quetzal-stack-frame previous-fp previous-sp stack))
    (malyon-write-byte-to-file (lsh return-addr -16))
    (malyon-write-byte-to-file (lsh return-addr -8))
    (malyon-write-byte-to-file return-addr)
    (if (zerop frame-id)
  (malyon-write-byte-to-file 0)
      (malyon-write-byte-to-file (logior (if result-addr 0 16)
           (length local-vars))))
    (malyon-write-byte-to-file (if result-addr result-addr 0))
    (malyon-write-byte-to-file (- (lsh 1 num-args) 1))
    (malyon-write-word-to-file (length eval-stack))
    (while (not (null local-vars))
      (malyon-write-word-to-file (car local-vars))
      (setq local-vars (cdr local-vars)))
    (while (not (null eval-stack))
      (malyon-write-word-to-file (car eval-stack))
      (setq eval-stack (cdr eval-stack)))))

;; restoring data from disk

(defvar malyon-restore-data-error nil
  "An error message if restoring data from a file failed.")

(defvar malyon-restore-quetzal-stack nil
  "A temporary stack for restoring quetzal game states.")

(defvar malyon-restore-quetzal-stack-pointer nil
  "A temporary stack pointer for restoring quetzal game states.")

(defvar malyon-restore-quetzal-frame-pointer nil
  "A temporary frame-pointer for restoring quetzal game states.")

(defun malyon-restore-file (file &optional table length)
  "Restore a game state or a memory section from disk."
  (interactive "fLoad file: ")
  (if (not (and (file-exists-p file) (file-readable-p file)))
      0
    (condition-case nil
  (save-excursion
    (setq malyon-restore-data-error nil)
    (set-buffer (create-file-buffer file))
    (malyon-disable-multibyte)
    (malyon-erase-buffer)
    (let ((coding-system-for-read 'binary))
      (insert-file-contents file))
    (goto-char (point-min))
    (if table
        (malyon-restore-table table length)
      (let* ((first  (malyon-read-chunk-id-from-file))
       (second (malyon-read-dword-from-file))
       (third  (malyon-read-chunk-id-from-file)))
        (if (and (string= "FORM" first) (string= "IFZS" third))
      (malyon-restore-quetzal-state (+ 8 second))
    (goto-char (point-min))
    (malyon-restore-game-state))))
    (kill-buffer nil)
    (if (null malyon-restore-data-error)
        2
      (message malyon-restore-data-error)
      0))
      (error 0))))

(defun malyon-restore-table (table length)
  "Restore the given section of memory from a file."
  (let ((i 0)
  (j table))
    (while (< i length)
      (malyon-store-byte j (malyon-read-byte-from-file))
      (setq i (+ 1 i)
      j (+ 1 j)))))

(defun malyon-restore-game-state ()
  "Restore a saved game state from disk."
  (let ((len   0)
  (name  0)
  (story 0)
  (ip    0)
  (sp    0)
  (fp    0)
  (dyn   0)
  (stack (copy-sequence malyon-stack))
  (mem   (copy-sequence malyon-story-file))
  (i     0))
    (setq len (malyon-read-word-from-file))
    (setq name (make-string len ? ))
    (while (< i len)
      (aset name i (malyon-read-byte-from-file))
      (setq i (+ 1 i)))
    (setq ip  (malyon-read-dword-from-file))
    (setq sp  (malyon-read-word-from-file))
    (setq fp  (malyon-read-word-from-file))
    (setq dyn (malyon-read-word-from-file))
    (setq i 0)
    (while (<= i sp)
      (aset stack i (malyon-read-dword-from-file))
      (setq i (+ 1 i)))
    (setq i 0)
    (while (< i dyn)
      (aset mem i (malyon-read-byte-from-file))
      (setq i (+ 1 i)))
    (setq name  (file-name-nondirectory name))
    (setq story (file-name-nondirectory malyon-story-file-name))
    (if (or (string-match name story) (string-match story name))
  (malyon-set-game-state (vector ip sp fp stack mem nil))
      (setq malyon-restore-data-error "Invalid save file."))))

(defun malyon-restore-quetzal-state (size)
  "Restore a saved quetzal game state from disk."
  (let ((chunk-id  nil)
  (chunk-len 0)
  (ip        0)
  (memory    nil)
  (stack     nil)
  (beginning 0))
    (while (< (point) size)
      (setq chunk-id  (malyon-read-chunk-id-from-file))
      (setq chunk-len (malyon-read-dword-from-file))
      (setq beginning (point))
      (cond ((string= chunk-id "IFhd")
       (setq ip (malyon-restore-quetzal-ifhd chunk-len)))
      ((string= chunk-id "CMem")
       (setq memory (malyon-restore-quetzal-cmem chunk-len)))
      ((string= chunk-id "UMem")
       (setq memory (malyon-restore-quetzal-umem chunk-len)))
      ((string= chunk-id "Stks")
       (setq stack (malyon-restore-quetzal-stks chunk-len))))
      (if (zerop (mod chunk-len 2)) '() (setq chunk-len (+ 1 chunk-len)))
      (goto-char (+ beginning chunk-len)))
    (cond ((and ip memory stack)
     (malyon-set-game-state (vector ip
            (aref stack 0)
            (aref stack 1)
            (aref stack 2)
            memory
            t)))
    ((null malyon-restore-data-error)
     (setq malyon-restore-data-error "invalid quetzal file.")))))

(defun malyon-restore-quetzal-ifhd (size)
  "Restore an IFhd chunk from disk. Return the instruction pointer."
  (if (and (= (malyon-read-word-from-file) (malyon-read-word 2))
     (= (malyon-read-word-from-file) (malyon-read-word 18))
     (= (malyon-read-word-from-file) (malyon-read-word 20))
     (= (malyon-read-word-from-file) (malyon-read-word 22))
     (= (malyon-read-word-from-file) (malyon-read-word 28)))
      (logior (lsh (malyon-read-byte-from-file) 16)
        (lsh (malyon-read-byte-from-file) 8)
        (malyon-read-byte-from-file))
    (setq malyon-restore-data-error "quetzal file doesn't belong to game.")
    nil))

(defun malyon-restore-quetzal-cmem (size)
  "Restore a CMem chunk from disk. Return the entire memory layout."
  (let ((memory   (copy-sequence (aref malyon-game-state-restart 4)))
  (max-size (+ (point) size))
  (byte     0)
  (i        0))
    (while (< (point) max-size)
      (setq byte (malyon-read-byte-from-file))
      (if (zerop byte)
    (setq i (+ 1 i (malyon-read-byte-from-file)))
  (aset memory i (logxor byte (aref memory i)))
  (setq i (+ 1 i))))
    memory))

(defun malyon-restore-quetzal-umem (size)
  "Restore a UMem chunk from disk. Return the entire memory layout."
  (let ((memory (copy-sequence (aref malyon-game-state-restart 4)))
  (i      0))
    (while (< i size)
      (aset memory i (malyon-read-byte-from-file))
      (setq i (+ 1 i)))
    memory))

(defun malyon-restore-quetzal-stks (size)
  "Restore a Stks chunk from disk. Return a vector containing the 
stack pointer, the frame pointer, and the stack itself."
  (let ((i 0) (frame-id 0))
    (setq malyon-restore-quetzal-stack
    (copy-sequence (aref malyon-game-state-restart 3)))
    (setq malyon-restore-quetzal-stack-pointer -1)
    (setq malyon-restore-quetzal-frame-pointer 2)
    (while (< i size)
      (let* ((beginning     (point))
       (return3       (malyon-read-byte-from-file))
       (return2       (malyon-read-byte-from-file))
       (return1       (malyon-read-byte-from-file))
       (return-addr   (logior (lsh return3 16) (lsh return2 8) return1))
       (result-locals (malyon-read-byte-from-file))
       (has-result    (zerop (logand 16 result-locals)))
       (num-locals    (logand 15 result-locals))
       (result-addr   (malyon-read-byte-from-file))
       (arg-flags     (+ 1 (malyon-read-byte-from-file)))
       (num-args      0)
       (eval-size     (malyon-read-word-from-file))
       (local-vars    '())
       (eval-stack    '()))
  (while (> num-locals 0)
    (setq local-vars (cons (malyon-read-word-from-file) local-vars))
    (setq num-locals (- num-locals 1)))
  (while (> eval-size 0)
    (setq eval-stack (cons (malyon-read-word-from-file) eval-stack))
    (setq eval-size (- eval-size 1)))
  (while (> arg-flags 1)
    (setq arg-flags (lsh arg-flags -1))
    (setq num-args (+ num-args 1)))
  (malyon-push-stack-frame frame-id
         return-addr
         (if (zerop frame-id)
             nil
           (if has-result result-addr nil))
         (reverse local-vars)
         num-args
         (reverse eval-stack))
  (setq frame-id (+ 1 frame-id))
  (setq i (+ i (- (point) beginning)))))
    (vector malyon-restore-quetzal-stack-pointer
      malyon-restore-quetzal-frame-pointer
      malyon-restore-quetzal-stack)))

;; object table management

(defsubst malyon-object-address (object)
  "Compute the address at which the object is stored."
  (+ malyon-object-table
     (* 2 malyon-object-properties)
     (* malyon-object-table-entry-size (- object 1))))

(defsubst malyon-object-read-parent (address)
  "Return the parent."
  (if (< malyon-story-version 5)
      (malyon-read-byte (+ 4 address))
    (malyon-read-word (+ 6 address))))

(defsubst malyon-object-read-sibling (address)
  "Return the next sibling."
  (if (< malyon-story-version 5)
      (malyon-read-byte (+ 5 address))
    (malyon-read-word (+ 8 address))))

(defsubst malyon-object-read-child (address)
  "Return the first child."
  (if (< malyon-story-version 5)
      (malyon-read-byte (+ 6 address))
    (malyon-read-word (+ 10 address))))

(defsubst malyon-object-store-parent (address value)
  "Set the parent."
  (if (< malyon-story-version 5)
      (malyon-store-byte (+ 4 address) value)
    (malyon-store-word (+ 6 address) value)))

(defsubst malyon-object-store-sibling (address value)
  "Set the next sibling."
  (if (< malyon-story-version 5)
      (malyon-store-byte (+ 5 address) value)
    (malyon-store-word (+ 8 address) value)))

(defsubst malyon-object-store-child (address value)
  "Set the first child."
  (if (< malyon-story-version 5)
      (malyon-store-byte (+ 6 address) value)
    (malyon-store-word (+ 10 address) value)))

(defun malyon-find-property (object property)
  "Return the address of the object's property, or 0 if it doesn't exist."
  (let ((next (malyon-first-property object))
  (number 0))
    (setq number (logand (malyon-read-byte next) malyon-object-properties))
    (while (> number property)
      (setq next (malyon-next-property next))
      (setq number (logand (malyon-read-byte next) malyon-object-properties)))
    (if (= number property) next 0)))

(defun malyon-first-property (object)
  "Get the address of the object's first property."
  (let ((header (malyon-read-word (+ malyon-object-property-offset
             (malyon-object-address object)))))
    (+ header 1 (* 2 (malyon-read-byte header)))))

(defun malyon-next-property (property)
  "Get the address of the following property."
  (let ((size (malyon-read-byte property))
  (addr (+ property 1)))
    (+ 1 addr (cond ((< malyon-story-version 5) (lsh size -5))
        ((zerop (logand 128 size))  (lsh size -6))
        (t
         (let ((second (logand 63 (malyon-read-byte addr))))
           (if (= 0 second) 64 second)))))))

(defun malyon-remove-object (object)
  "Remove the object from the children list of its parent."
  (let* ((address (malyon-object-address object))
   (parent  (malyon-object-read-parent address))
   (sibling (malyon-object-read-sibling address)))
    (malyon-object-store-parent address 0)
    (malyon-object-store-sibling address 0)
    (if (/= parent 0)
  (let ((parent-addr (malyon-object-address parent)))
    (let ((children (malyon-object-read-child parent-addr)))
      (if (or (= children 0) (= children object))
    (malyon-object-store-child parent-addr sibling)
        (let ((this (malyon-object-address children)))
    (let ((next (malyon-object-read-sibling this)))
      (while (/= next object)
        (setq this (malyon-object-address next))
        (setq next (malyon-object-read-sibling this)))
      (malyon-object-store-sibling this sibling)))))))))

;; function calls and code branches

(defun malyon-call-routine (routine arguments &optional result)
  "Call a routine with the given arguments and return its result."
  (if (= routine 0)
      (if result (malyon-store-variable result 0) 0)
    (malyon-push-stack (if result 0 1))
    (malyon-push-stack (if result result 0))
    (malyon-push-stack malyon-instruction-pointer)
    (malyon-push-stack
     (logior (lsh (- malyon-stack-pointer malyon-frame-pointer) 8)
       (length arguments)))
    (setq malyon-instruction-pointer (* malyon-packed-multiplier routine))
    (let ((args (malyon-read-code-byte)) (value nil))
      (if malyon-game-state-quetzal
    (let ((id (lsh (aref malyon-stack malyon-frame-pointer) -8)))
      (malyon-push-stack (logior (lsh (+ 1 id) 8) args))))
      (setq malyon-frame-pointer malyon-stack-pointer)
      (while (> args 0)
  (setq value (if (< malyon-story-version 5) (malyon-read-code-word) 0))
  (malyon-push-stack (if (null arguments) value (car arguments)))
  (setq arguments (cdr arguments))
  (setq args (- args 1))))))

(defun malyon-jump-if (condition)
  "Jump depending on the condition and the following jump data."
  (let ((byte   (malyon-read-code-byte))
  (offset nil)
  (iftrue nil))
    (setq iftrue (/= 0 (logand byte 128)))
    (setq offset (logand byte 63))
    (if (= 0 (logand byte 64)) 
  (progn
    (setq offset (logior (lsh offset 8) (malyon-read-code-byte)))
    (if (>= offset 8192) (setq offset (- offset 16384)))))
    (if (or (and iftrue condition) (and (not iftrue) (not condition)))
  (progn
    (cond ((= offset 0) (malyon-opcode-rfalse))
    ((= offset 1) (malyon-opcode-rtrue))
    (t            (setq
             malyon-instruction-pointer 
             (+ malyon-instruction-pointer offset -2))))))))

(defun malyon-return (value)
  "Return from a routine."
  (setq malyon-stack-pointer malyon-frame-pointer)
  (if malyon-game-state-quetzal (malyon-pop-stack))
  (setq malyon-frame-pointer
  (- malyon-stack-pointer 1 (lsh (malyon-pop-stack) -8)))
  (setq malyon-instruction-pointer (malyon-pop-stack))
  (let ((result (malyon-pop-stack))
  (store  (malyon-pop-stack)))
    (if (zerop store)
  (malyon-return-store result value)
      (malyon-return-ignore result value))))

(defun malyon-return-ignore (where value)
  "Return from a routine ignoring the result.")

(defun malyon-return-store (where value)
  "Return from a routine storing the result."
  (malyon-store-variable where value))

(defun malyon-push-initial-frame ()
  "Push the initial stack frame required in quetzal mode."
  (if malyon-game-state-quetzal
      (progn
  (malyon-push-stack 1)
  (malyon-push-stack 0)
  (malyon-push-stack 0)
  (malyon-push-stack 0)
  (malyon-push-stack 0))))

(defun malyon-get-stack-frame (fp sp stack)
  "Return a decoded stack frame in quetzal mode.
The result is a vector containing the frame id, the fp of the
previous frame, the sp of the previous frame, the return address,
the result variable if any, a list of local variables, the number
of arguments, and a list of the evaluation stack elements."
  (let* ((has-result   (zerop (aref stack fp)))
   (result-addr  (if has-result (aref stack (+ 1 fp)) nil))
   (return-addr  (aref stack (+ 2 fp)))
   (offset       (lsh (aref stack (+ 3 fp)) -8))
   (num-args     (logand 255 (aref stack (+ 3 fp))))
   (frame-id     (lsh (aref stack (+ 4 fp)) -8))
   (num-locals   (logand 255 (aref stack (+ 4 fp))))
   (start-locals (+ 5 fp))
   (start-eval   (+ 5 fp num-locals))
   (local-vars   '())
   (eval-stack   '()))
    (if (not (zerop num-locals))
  (setq local-vars 
        (malyon-vector-to-list stack start-locals start-eval)))
    (if (> sp start-eval)
  (setq eval-stack 
        (malyon-vector-to-list stack start-eval (+ 1 sp))))
    (vector frame-id
      (- fp offset 2)
      (- fp 1)
      return-addr
      result-addr
      local-vars
      num-args
      eval-stack)))

(defsubst malyon-restore-quetzal-push-stack (value)
  "Push a value onto the restore quetzal stack."
  (setq malyon-restore-quetzal-stack-pointer
  (+ malyon-restore-quetzal-stack-pointer 1))
  (aset malyon-restore-quetzal-stack
  malyon-restore-quetzal-stack-pointer
  value))

(defun malyon-push-stack-frame
  (frame-id return-addr result local-vars num-args eval-stack)
  "Pushes a new stack frame in quetzal mode."
  (malyon-restore-quetzal-push-stack (if result 0 1))
  (malyon-restore-quetzal-push-stack (if result result 0))
  (malyon-restore-quetzal-push-stack return-addr)
  (malyon-restore-quetzal-push-stack
   (logior (lsh (- malyon-restore-quetzal-stack-pointer
       malyon-restore-quetzal-frame-pointer) 8)
     num-args))
  (malyon-restore-quetzal-push-stack
   (logior (lsh frame-id 8) (length local-vars)))
  (setq malyon-restore-quetzal-frame-pointer
  malyon-restore-quetzal-stack-pointer)
  (while (not (null local-vars))
    (malyon-restore-quetzal-push-stack (car local-vars))
    (setq local-vars (cdr local-vars)))
  (while (not (null eval-stack))
    (malyon-restore-quetzal-push-stack (car eval-stack))
    (setq eval-stack (cdr eval-stack))))

;; other stuff

(defvar malyon-aread-text nil
  "Text buffer for user input.")

(defvar malyon-aread-parse nil
  "Parse buffer for user input.")

(defvar malyon-aread-beginning-of-line nil
  "The beginning of the input line.")

;; execution

(defun malyon-interpreter ()
  "Run the z code interpreter on the given story file."
  (condition-case nil
      (progn
  (malyon-restore-window-configuration)
  (if malyon-story-file
      (catch 'malyon-end-of-interpreter-loop
        (setq malyon-last-cursor-position-after-input
        (malyon-point-max malyon-transcript-buffer))
        (malyon-execute))))
    (error
     (malyon-fatal-error "unspecified internal runtime error."))))

(defsubst malyon-fetch-variable-operands (specifier)
  "Fetch a variable number of operands based on the specifier argument."
  (let ((var  (logand specifier 49152))
  (op   '()))
    (setq specifier (logand 65535 specifier))
    (while (/= 0 specifier)
      (cond ((= var 0)     (setq op (cons (malyon-read-code-word) op)))
      ((= var 16384) (setq op (cons (malyon-read-code-byte) op)))
      ((= var 32768) (setq op (cons (malyon-read-variable
             (malyon-read-code-byte)) op)))
      (t             (setq specifier 0)))
      (setq specifier (logand 65535 (lsh specifier 2)))
      (setq var  (logand specifier 49152)))
    (nreverse op)))

(defsubst malyon-fetch-extended (opcode)
  "Fetch operands for an extended instruction."
  (malyon-fetch-variable-operands
   (logior (lsh (malyon-read-code-byte) 8) 255)))

(defsubst malyon-fetch-variable (opcode)
  "Fetch operands for a variable instruction."
  (malyon-fetch-variable-operands
   (if (or (= opcode 236) (= opcode 250))
       (malyon-read-code-word)
     (logior (lsh (malyon-read-code-byte) 8) 255))))

(defsubst malyon-fetch-short (opcode)
  "Fetch operands for a short instruction."
  (let ((op (logand opcode 48)))
    (cond ((= op 0)  (list (malyon-read-code-word)))
    ((= op 16) (list (malyon-read-code-byte)))
    ((= op 32) (list (malyon-read-variable (malyon-read-code-byte)))))))

(defsubst malyon-fetch-long (instr)
  "Fetch operands for a long instruction."
  (let ((byte1 (malyon-read-code-byte))
  (byte2 (malyon-read-code-byte)))
    (list (if (= (logand instr 64) 0) byte1 (malyon-read-variable byte1))
    (if (= (logand instr 32) 0) byte2 (malyon-read-variable byte2)))))

(defun malyon-execute ()
  "Execute z code instructions.
Load the next instruction opcode and its operands and execute it.
Repeat ad infinitum."
  (let ((opcode) (operands)); (pc))
    (while t
;      (setq pc malyon-instruction-pointer)
      (setq opcode (malyon-read-code-byte))
      (setq operands (cond ((=  opcode 190) 
          (setq opcode (+ 256 (malyon-read-code-byte)))
          (malyon-fetch-extended opcode))
         ((>= opcode 192)
          (malyon-fetch-variable opcode))
         ((>= opcode 128)
          (malyon-fetch-short opcode))
         (t
          (malyon-fetch-long opcode))))
;      (malyon-trace-opcode pc opcode operands)
      (apply (aref malyon-opcodes opcode) operands))))

;; opcodes

(defsubst malyon-number (n)
  "Convert an unsigned number into a signed one."
  (if (< n 32768) n (- n 65536)))

(defun malyon-opcode-add (a b)
  "Addition."
  (malyon-store-variable (malyon-read-code-byte)
       (+ (malyon-number a) (malyon-number b))))

(defun malyon-opcode-and (a b)
  "Bitwise and."
  (malyon-store-variable (malyon-read-code-byte) (logand a b)))

(defun malyon-opcode-aread (text parse &optional time routine)
  "Read input text."
  (setq malyon-aread-text text)
  (setq malyon-aread-parse parse)
  (goto-char (point-max))
  (setq malyon-aread-beginning-of-line (point))
; Some games violate these assumptions for the "Quit" question.
;  (if (> 3 (malyon-read-byte text))
;      (malyon-fatal-error "text buffer less than 3 bytes."))
;  (if (and (not (zerop parse)) (> 2 (malyon-read-byte parse)))
;      (malyon-fatal-error "parse buffer less than 2 bytes."))
  (malyon-more malyon-keymap-read)
  (throw 'malyon-end-of-interpreter-loop 'malyon-waiting-for-input))

(defun malyon-opcode-art-shift (value places)
  "Arithmetic shift."
  (malyon-store-variable (malyon-read-code-byte) (ash value places)))

(defun malyon-opcode-buffer-mode (mode)
  "Toggles buffering of text in the transcript window."
  (setq malyon-transcript-buffer-buffered (/= 0 mode)))

(defun malyon-opcode-calln (routine &rest arguments)
  "Call a routine and ignore the result."
  (malyon-call-routine routine arguments))

(defun malyon-opcode-calls (routine &rest arguments)
  "Call a routine and store the result."
  (malyon-call-routine routine arguments (malyon-read-code-byte)))

(defun malyon-opcode-catch ()
  "Return the current stack frame."
  (malyon-store-variable
   (malyon-read-code-byte)
   (if malyon-game-state-quetzal
       (lsh (aref malyon-stack malyon-frame-pointer) -8)
     malyon-frame-pointer)))

(defun malyon-opcode-check-arg-count (count)
  "Tests the number of arguments passed to routine."
  (malyon-jump-if
   (<= count (logand 255 (aref malyon-stack 
             (if malyon-game-state-quetzal
           (- malyon-frame-pointer 1)
         malyon-frame-pointer))))))

(defun malyon-opcode-check-unicode (char)
  "Check whether the given character is valid for input/output."
  (malyon-store-variable (malyon-read-code-byte) 0))

(defun malyon-opcode-clear-attr (object attribute)
  "Clear the given attribute in the given object."
  (let ((attributes (malyon-object-address object))
  (byte       (lsh attribute -3)))
    (malyon-store-byte (+ attributes byte)
           (logand (malyon-read-byte (+ attributes byte))
             (logxor (lsh 128 (- (logand attribute 7)))
               255)))))

(defun malyon-opcode-copy-table (first second size)
  "Copies first table onto second one."
  (let* ((length  (abs (malyon-number size)))
   (zero    (zerop second))
   (forward (or (< (malyon-number size) 0) (> first second)))
   (i       0)
   (a       (if forward first (+ first length -1)))
   (b       (if forward (if zero first second) (+ second length -1))))
    (while (< i length)
      (malyon-store-byte b (if zero 0 (malyon-read-byte a)))
      (setq i (+ i 1)
      a (if forward (+ a 1) (- a 1))
      b (if forward (+ b 1) (- b 1))))))

(defun malyon-opcode-dec (var)
  "Decrement variable."
  (malyon-store-variable var
       (- (malyon-number (malyon-read-variable var)) 1)))

(defun malyon-opcode-dec-chk (variable threshold)
  "Decrement variable and jump if it's less than the given value."
  (let ((value (malyon-number (malyon-read-variable variable))))
    (malyon-store-variable variable (- value 1))
    (malyon-jump-if (< (- value 1) (malyon-number threshold)))))

(defun malyon-opcode-div (a b)
  "Division."
  (if (zerop b) (malyon-fatal-error "division by 0."))
  (malyon-store-variable (malyon-read-code-byte)
       (/ (malyon-number a) (malyon-number b))))

(defun malyon-opcode-encode-text (text length from encoded)
  "Encode the zscii text starting at from with the given length.
The result is stored at encoded."
  (let* ((i     length)
   (j     encoded)
   (l     '())
   (word  '()))
    (while (< 0 i)
      (setq l (cons (malyon-read-byte (+ text from i -1)) l)
      i (- i 1)))
    (setq word (malyon-encode-dictionary-word
    (append (malyon-mapcan 'malyon-encode-into-ztext l)
      '(5 5 5 5 5 5 5 5))))
    (while (< i 6)
      (malyon-store-byte j (car l))
      (setq i (+ 1 i)
      j (+ 1 j)
      l (cdr word)))))

(defun malyon-opcode-erase-line (value)
  "Erases the rest of the line."
  (if (= value 1)
      (if (eq malyon-transcript-buffer (current-buffer))
    (kill-line nil)
  (save-excursion
    (let ((i (current-column)))
      (while (<= i malyon-max-column)
        (insert ? )
        (delete-char 1)
        (setq i (+ 1 i))))))))

(defun malyon-opcode-erase-window (window)
  "Erase the contents of the given window."
  (save-excursion
    (let ((w (malyon-number window)))
      (if (or (= w 0) (= w -1) (= w -2))
    (malyon-erase-buffer malyon-transcript-buffer))
      (if (or (= w 1) (= w -1) (= w -2))
    (malyon-erase-buffer malyon-status-buffer))
      (if (= w -1)
    (malyon-split-buffer-windows 0)))
    (setq malyon-last-cursor-position-after-input
    (malyon-point-max malyon-transcript-buffer))))

(defun malyon-opcode-get-child (object)
  "Get the first child of the given object and jump."
  (let ((child (malyon-object-read-child (malyon-object-address object))))
    (malyon-store-variable (malyon-read-code-byte) child)
    (malyon-jump-if (/= 0 child))))

(defun malyon-opcode-get-cursor (array)
  "Retrieves the current cursor position."
  (save-excursion
    (set-buffer malyon-status-buffer)
    (malyon-store-word array (- (count-lines (point-min) (point)) 1))
    (malyon-store-word (+ 2 array) (+ 1 (current-column)))))

(defun malyon-opcode-get-next-prop (object property)
  "Retrieve the first or next property id of object."
  (let ((next (malyon-first-property object))
  (number 0))
    (if (zerop property)
  '()
      (setq number (logand (malyon-read-byte next)
         malyon-object-properties))
      (setq next (malyon-next-property next))
      (while (> number property)
  (setq number (logand (malyon-read-byte next)
           malyon-object-properties))
  (setq next (malyon-next-property next)))
      (if (/= number property)
    (malyon-fatal-error "property does not exist.")))
    (setq number (logand (malyon-read-byte next) malyon-object-properties))
    (malyon-store-variable (malyon-read-code-byte) number)))
    
(defun malyon-opcode-get-parent (object)
  "Get the parent of the given object."
  (malyon-store-variable (malyon-read-code-byte)
       (malyon-object-read-parent
        (malyon-object-address object))))

(defun malyon-opcode-get-prop (object property)
  "Get the value of the object's property."
  (let* ((address (malyon-find-property object property))
   (size    (malyon-read-byte address)))
    (malyon-store-variable 
     (malyon-read-code-byte)
     (cond ((zerop address)
      (malyon-read-word (+ malyon-object-table (* 2 (- property 1)))))
     ((and (<  malyon-story-version 5) (zerop (lsh size -5)))
      (malyon-read-byte (+ address 1)))
     ((and (>= malyon-story-version 5) (zerop (logand 192 size)))
      (malyon-read-byte (+ address 1)))
     (t
      (malyon-read-word (+ address 1)))))))

(defun malyon-opcode-get-prop-addr (object property)
  "Get the address of the object's property."
  (let* ((address (malyon-find-property object property))
   (size    (malyon-read-byte address))
   (offset  (if (< malyon-story-version 5)
          1
        (if (zerop (logand 128 size)) 1 2))))
    (malyon-store-variable (malyon-read-code-byte)
         (if (zerop address) 0 (+ address offset)))))

(defun malyon-opcode-get-prop-len (property)
  "Get the length of the object's property."
  (let ((size (malyon-read-byte (- property 1))))
    (malyon-store-variable
     (malyon-read-code-byte)
     (cond ((< malyon-story-version 5) (+ 1 (lsh size -5)))
     ((zerop (logand 128 size))  (+ 1 (lsh size -6)))
     ((zerop (logand  63 size))  64)
     (t                          (logand 63 size))))))

(defun malyon-opcode-get-sibling (object)
  "Get the next object in the tree and jump."
  (let ((sibling (malyon-object-read-sibling (malyon-object-address object))))
    (malyon-store-variable (malyon-read-code-byte) sibling)
    (malyon-jump-if (/= 0 sibling))))

(defun malyon-opcode-illegal (&rest ignore)
  "Print an error message and exit the interpreter."
  (malyon-fatal-error "illegal opcode."))

(defun malyon-opcode-inc (var)
  "Increment variable."
  (malyon-store-variable var
       (+ (malyon-number (malyon-read-variable var)) 1)))

(defun malyon-opcode-inc-chk (variable threshold)
  "Increment variable and jump if it's greater than the given value."
  (let ((value (malyon-number (malyon-read-variable variable))))
    (malyon-store-variable variable (+ value 1))
    (malyon-jump-if (> (+ value 1) (malyon-number threshold)))))

(defun malyon-opcode-input-stream (number)
  "Select the given input stream. Only the keyboard is supported."
  (if (zerop (malyon-number number))
      '()
    (message "Only the keyboard is supported as an input stream.")))

(defun malyon-opcode-insert-obj (object destination)
  "Insert an object into the children list of another."
  (let ((child  (malyon-object-address object))
  (parent (malyon-object-address destination)))
    (malyon-remove-object object)
    (malyon-object-store-parent  child destination)
    (malyon-object-store-sibling child (malyon-object-read-child parent))
    (malyon-object-store-child   parent object)))

(defun malyon-opcode-je (a &rest rest)
  "Jump if first operand equals any of the following."
  (malyon-jump-if (member (malyon-number a) (mapcar 'malyon-number rest))))

(defun malyon-opcode-jg (a b)
  "Jump if first operand > second operand."
  (malyon-jump-if (> (malyon-number a) (malyon-number b))))

(defun malyon-opcode-jin (child parent)
  "Jump if second object is parent of the first one."
    (malyon-jump-if
     (= parent (malyon-object-read-parent (malyon-object-address child)))))

(defun malyon-opcode-jl (a b)
  "Jump if first operand < second operand."
  (malyon-jump-if (< (malyon-number a) (malyon-number b))))

(defun malyon-opcode-jump (offset)
  "Jump unconditionally."
  (setq malyon-instruction-pointer (+ malyon-instruction-pointer
              (malyon-number offset) -2)))

(defun malyon-opcode-jz (a)
  "Jump if operand = 0."
  (malyon-jump-if (zerop a)))

(defun malyon-opcode-load (variable)
  "Load a variable."
  (malyon-store-variable (malyon-read-code-byte)
       (malyon-read-variable variable)))

(defun malyon-opcode-loadb (array index)
  "Load an array element into a variable."
  (malyon-store-variable (malyon-read-code-byte)
       (malyon-read-byte (+ array index))))

(defun malyon-opcode-loadw (array index)
  "Load an array element into a variable."
  (malyon-store-variable (malyon-read-code-byte)
       (malyon-read-word (+ array (* 2 index)))))

(defun malyon-opcode-log-shift (value places)
  "Logical shift."
  (malyon-store-variable (malyon-read-code-byte) (lsh value places)))

(defun malyon-opcode-mod (a b)
  "Modulo."
  (malyon-store-variable (malyon-read-code-byte)
       (mod (malyon-number a) (malyon-number b))))

(defun malyon-opcode-mul (a b)
  "Multiplication."
  (malyon-store-variable (malyon-read-code-byte)
       (* (malyon-number a) (malyon-number b))))

(defun malyon-opcode-new-line ()
  "Print a newline."
  (malyon-newline))

(defun malyon-opcode-nop (&rest ignore)
  "Do nothing.")

(defun malyon-opcode-not (a)
  "Bitwise not."
  (malyon-store-variable (malyon-read-code-byte) (logand 65535 (lognot a))))

(defun malyon-opcode-or (a b)
  "Bitwise or."
  (malyon-store-variable (malyon-read-code-byte) (logior a b)))

(defun malyon-opcode-output-stream (stream &optional table)
  "Select an output stream."
  (let ((stream (malyon-number stream)))
    (cond ((< 0 stream) (malyon-add-output-stream stream table))
    ((> 0 stream) (malyon-remove-output-stream (- stream))))))

(defun malyon-opcode-piracy ()
  "Piracy check, effectively an unconditional jump."
  (malyon-jump-if 1))

(defun malyon-opcode-pop ()
  "Pop a value off the stack."
  (malyon-pop-stack))

(defun malyon-opcode-print ()
  "Print a string."
  (setq malyon-instruction-pointer
  (malyon-print-text malyon-instruction-pointer)))

(defun malyon-opcode-print-addr (address)
  "Print a string."
  (malyon-print-text address))

(defun malyon-opcode-print-char (c)
  "Print a character."
  (malyon-print (char-to-string c)))

(defun malyon-opcode-print-num (n)
  "Print a number."
  (malyon-print (number-to-string (malyon-number n))))

(defun malyon-opcode-print-obj (obj)
  "Print the short name of the object."
  (malyon-print-text
   (+ 1 (malyon-read-word (+ malyon-object-property-offset
           (malyon-object-address obj))))))

(defun malyon-opcode-print-paddr (address)
  "Print a string."
  (malyon-print-text (* malyon-packed-multiplier address)))

(defun malyon-opcode-print-ret ()
  "Print a string, print a newline, return true/1."
  (setq malyon-instruction-pointer
  (malyon-print-text malyon-instruction-pointer))
  (malyon-newline)
  (malyon-return 1))

(defun malyon-opcode-print-table (text width &optional height skip)
  "Print the given table."
  (if (not height) (setq height 1))
  (if (not skip)   (setq skip 0))
  (let ((column  (current-column))
  (address text)
  (y       0)
  (x       0))
    (while (< y height)
      (if (zerop y)
    '()
  (malyon-newline)
  (malyon-print-characters (make-string column ? )))
      (setq x 0)
      (while (< x width)
  (malyon-output-character (malyon-read-byte address))
  (setq address (+ 1 address))
  (setq x (+ 1 x)))
      (setq address (+ skip address))
      (setq y (+ 1 y)))))

(defun malyon-opcode-print-unicode (char)
  "Prints a unicode character.")

(defun malyon-opcode-pull (variable)
  "Pull value off stack."
  (malyon-store-variable variable (malyon-pop-stack)))

(defun malyon-opcode-push (value)
  "Push value onto stack."
  (malyon-push-stack value))

(defun malyon-opcode-put-prop (object property value)
  "Set the object's property to the given value."
  (let* ((address (malyon-find-property object property))
   (size    (malyon-read-byte address)))
    (cond ((= address 0)
     (malyon-fatal-error "property does not exist."))
    ((and (<  malyon-story-version 5) (zerop (lsh size -5)))
     (malyon-store-byte (+ 1 address) (logand 255 value)))
    ((and (>= malyon-story-version 5) (zerop (logand size 192)))
     (malyon-store-byte (+ 1 address) (logand 255 value)))
    (t
     (malyon-store-word (+ 1 address) value)))))

(defun malyon-opcode-quit ()
  "End the game immediately."
  (malyon-adjust-transcript)
  (malyon-cleanup)
  (throw 'malyon-end-of-interpreter-loop 'malyon-opcode-quit))

(defun malyon-opcode-random (limit)
  "Generate a random number or set the seed value."
  (malyon-store-variable (malyon-read-code-byte)
       (if (>= 0 (malyon-number limit))
           0
         (+ 1 (random (malyon-number limit))))))

(defun malyon-opcode-read-char (&optional device &rest ignore)
  "Read a character."
  (if (and device (/= 1 device))
      (malyon-fatal-error "illegal device specified in read_char."))
  (if (eq malyon-transcript-buffer (current-buffer))
      (goto-char (point-max)))
  (message "[Press a key.]")
  (malyon-more malyon-keymap-readchar)
  (throw 'malyon-end-of-interpreter-loop 'malyon-waiting-for-character))

(defun malyon-opcode-remove-obj (object)
  "Remove an object from its parent's children list."
  (malyon-remove-object object))

(defun malyon-opcode-restart ()
  "Restart the game."
  (malyon-set-game-state malyon-game-state-restart))

(defun malyon-opcode-restore (&optional table bytes name)
  "Restore a saved game state or a section of memory from a file."
  (let ((result (if name
        (malyon-restore-file
         (malyon-get-file-name name) table bytes)
      (call-interactively 'malyon-restore-file))))
    (if (< malyon-story-version 5)
  (malyon-jump-if (not (zerop result)))
      (malyon-store-variable (malyon-read-code-byte) result))))

(defun malyon-opcode-restore-undo ()
  "Restore game state for undo."
  (if malyon-game-state-undo
      (malyon-set-game-state malyon-game-state-undo))
  (malyon-store-variable (malyon-read-code-byte) 2))

(defun malyon-opcode-ret (value)
  "Return a value."
  (malyon-return value))

(defun malyon-opcode-ret-popped ()
  "Return top of stack."
  (malyon-return (malyon-pop-stack)))

(defun malyon-opcode-rfalse ()
  "Return false/0."
  (malyon-return 0))

(defun malyon-opcode-rtrue ()
  "Return true/1."
  (malyon-return 1))

(defun malyon-opcode-save (&optional table bytes name)
  "Save the current game state or a section of memory to a file."
  (let ((result (if name
        (malyon-save-file (malyon-get-file-name name) table bytes)
      (call-interactively 'malyon-save-file))))
    (if (< malyon-story-version 5)
  (malyon-jump-if (not (zerop result)))
      (malyon-store-variable (malyon-read-code-byte) result))))

(defun malyon-opcode-save-undo ()
  "Save game state for undo."
  (setq malyon-game-state-undo (malyon-current-game-state))
  (malyon-store-byte (malyon-read-code-byte) 1))

(defun malyon-opcode-scan-table (x table len &optional form)
  "Scan the given table for the first occurrence of x."
  (if (not form) (setq form 130))
  (let ((inc (logand 127 form))
  (byte (zerop (logand 128 form)))
  (addr table)
  (found 0)
  (index 0))
    (while (and (zerop found) (< index len))
      (setq found
      (if byte
    (if (= x (malyon-read-byte addr)) addr 0)
        (if (= x (malyon-read-word addr)) addr 0)))
      (setq addr (+ addr inc))
      (setq index (+ index 1)))
    (malyon-store-variable (malyon-read-code-byte) found)
    (malyon-jump-if (not (zerop found)))))

(defun malyon-opcode-set-attr (object attribute)
  "Set the given attribute in the given object."
  (let ((attributes (malyon-object-address object))
  (byte       (lsh attribute -3)))
    (malyon-store-byte (+ attributes byte)
           (logior (malyon-read-byte (+ attributes byte))
             (lsh 128 (- (logand attribute 7)))))))

(defun malyon-opcode-set-color (foreground background)
  "Sets the fore- and background colors ie. does nothing.")

(defun malyon-opcode-set-cursor (&optional line column)
  "Set the cursor."
  (if (eq malyon-transcript-buffer (current-buffer))
      (goto-char (point-max))
    (if malyon-status-buffer-delayed-split
  (progn
    (malyon-split-buffer-windows malyon-status-buffer-delayed-split)
    (other-window 1)))
    (if line   '() (setq line   (count-lines (point-min) (point))))
    (if column '() (setq column (current-column)))
    (if (> line malyon-status-buffer-lines)
  (progn
    (malyon-split-buffer-windows line)
    (other-window 1)))
    (goto-char (point-min))
    (if (and (<= 1 line) (<= line malyon-status-buffer-lines))
  (forward-line line)
      (beginning-of-line))
    (if (and (<= 1 column) (<= column malyon-max-column))
  (forward-char (- column 1))
      (beginning-of-line))
    (setq malyon-status-buffer-point (point))))

(defun malyon-opcode-set-font (font)
  "Sets the font if available or 0 otherwise."
  (malyon-store-variable (malyon-read-code-byte) 0))

(defun malyon-opcode-set-text-style (style)
  "Set the text style/face."
  (let ((face (assq style malyon-faces)))
    (setq malyon-current-face (if face (cdr face) 'malyon-face-plain))))

(defun malyon-opcode-set-window (window)
  "Set the current window."
  (malyon-restore-window-configuration)
  (setq malyon-current-window window)
  (malyon-update-output-streams)
  (if (zerop window)
      (if (not (eq malyon-transcript-buffer (current-buffer)))
    (other-window 1))
    (if (not (eq malyon-status-buffer (current-buffer)))
  (other-window 1))
    (malyon-opcode-set-cursor 1 1)))

(defun malyon-opcode-show-status ()
  "Display the status line."
  (save-excursion
    (malyon-opcode-split-window 1)
    (malyon-restore-window-configuration)
    (malyon-opcode-set-window 1)
    (malyon-prepare-status-buffer 1)
    (malyon-opcode-set-cursor 1 1)
    (malyon-opcode-print-obj (malyon-read-global-variable 0))
    (if (<= (current-column) (- (current-fill-column) 10))
  (let* ((x     (malyon-read-global-variable 1))
         (y     (malyon-read-global-variable 2))
         (hours (if (> x 12) (- x 12) x))
         (ampm  (if (> x 12) "PM" "AM"))
         (score (format "%4d/%4d" x y))
         (time  (format "%02d:%02d%s" hours y ampm)))
    (malyon-opcode-set-cursor 1 (- (current-fill-column) 10))
    (malyon-print (if malyon-score-game score time))))
    (malyon-opcode-set-window 0)
    (malyon-adjust-transcript)))

(defun malyon-opcode-split-window (size)
  "Split upper and lower window."
  (malyon-set-window-configuration size))

(defun malyon-opcode-store (variable value)
  "Store a value in a variable."
  (malyon-store-variable variable value))

(defun malyon-opcode-storeb (array index value)
  "Store a value in an array at the given index."
  (malyon-store-byte (+ array index) value))

(defun malyon-opcode-storew (array index value)
  "Store a value in an array at the given index."
  (malyon-store-word (+ array (* 2 index)) value))

(defun malyon-opcode-sub (a b)
  "Subtraction."
  (malyon-store-variable (malyon-read-code-byte)
       (- (malyon-number a) (malyon-number b))))

(defun malyon-opcode-test (bitmap flags)
  "Test if all of the flags are set in the bitmap."
  (malyon-jump-if (= flags (logand bitmap flags))))

(defun malyon-opcode-test-attr (object attribute)
  "Jump depending on the given attribute in the given object."
  (malyon-jump-if 
   (/= 0 (logand (malyon-read-byte (+ (malyon-object-address object)
              (lsh attribute -3)))
     (lsh 128 (- (logand attribute 7)))))))

(defun malyon-opcode-throw (value frame)
  "Return from the given stack frame."
  (if malyon-game-state-quetzal
      (let ((id (lsh (aref malyon-stack malyon-frame-pointer) -8)))
  (while (/= frame id)
    (setq malyon-stack-pointer malyon-frame-pointer)
    (malyon-pop-stack)
    (setq malyon-frame-pointer
    (- malyon-stack-pointer 1 (lsh (malyon-pop-stack) -8)))
    (malyon-pop-stack)
    (malyon-pop-stack)
    (setq id (lsh (aref malyon-stack malyon-frame-pointer) -8))))
    (setq malyon-frame-pointer frame))
  (malyon-return value))

(defun malyon-opcode-tokenise (text parse &optional dict flag)
  "Perform lexical analysis on the text buffer."
  (let* ((words (malyon-text-to-words text dict))
   (word  (car           words))
   (start (car           word))
   (len   (malyon-cadr   word))
   (code  (malyon-caddr  word))
   (entry (malyon-lookup dict code))
   (i     0))
    (while (not (or (null words) (= i (malyon-read-byte parse))))
      (if (and (zerop entry) flag (/= 0 flag))
    '()
  (malyon-store-word (+ 2 parse (* 4 i)) entry)
  (malyon-store-byte (+ 4 parse (* 4 i)) len)
  (malyon-store-byte (+ 5 parse (* 4 i)) start))
      (setq words (cdr           words)
      word  (car           words)
      start (car           word)
      len   (malyon-cadr   word)
      code  (malyon-caddr  word)
      entry (malyon-lookup dict code)
      i     (+ 1 i)))
    (malyon-store-byte (+ 1 parse) i)))

(defun malyon-opcode-verify ()
  "Verify the correctness of the story file."
  (let ((length (+ 1 (* malyon-packed-multiplier (malyon-read-word 26))))
  (sum    0)
  (i      64))
    (while (< i length)
      (setq sum (mod (+ sum (malyon-read-byte i)) 65536)
      i   (+ 1 i)))
    (malyon-jump-if (= (malyon-read-word 28) sum))))

;; keymap utilities

(defun malyon-end-input ()
  "Store the input line in a text buffer and perform lexical analysis."
  (interactive)
  (condition-case nil
      (progn
  (malyon-adjust-transcript)
  (switch-to-buffer malyon-transcript-buffer)
  (goto-char (point-max))
  (let* ((input (downcase
           (buffer-substring-no-properties
      (if (< malyon-aread-beginning-of-line (point))
          malyon-aread-beginning-of-line
        (point))
      (point))))
         (vec  (malyon-string-to-vector input))
         (text (apply 'vector (mapcar 'malyon-unicode-to-zscii vec)))
         (len  (min (malyon-read-byte malyon-aread-text) (length text)))
         (i    0))
    (malyon-history-insert input)
    (if (>= malyon-story-version 5)
        (malyon-store-byte (+ malyon-aread-text 1) len))
    (while (< i len)
      (malyon-store-byte
       (+ malyon-aread-text (if (< malyon-story-version 5) 1 2) i)
       (malyon-char-to-int (aref text i)))
      (setq i (+ 1 i)))
    (if (< malyon-story-version 5)
        (malyon-store-byte (+ malyon-aread-text 1 len) 0)))
  (if (/= 0 malyon-aread-parse)
      (malyon-opcode-tokenise malyon-aread-text malyon-aread-parse))
  (newline)
  (if (>= malyon-story-version 5)
      (malyon-store-variable (malyon-read-code-byte) 10))
  (malyon-interpreter))
    (error
     (malyon-fatal-error "unspecified internal runtime error."))))

(defun malyon-more-char ()
  "Page down in More mode."
  (interactive)
  (condition-case nil
      (scroll-up)
    (error))
  (if (>= (count-lines (point) (point-max))
          (malyon-window-displayed-height))
      (message "[More]")
    (goto-char (point-max))
    (malyon-adjust-transcript)
    (use-local-map malyon-more-continue-keymap)))

(defun malyon-more-char-status ()
  "Wait for a key then continue."
  (interactive)
  (condition-case nil
      (progn
  (malyon-adjust-transcript)
  (use-local-map malyon-more-continue-keymap)
  (malyon-interpreter))
    (error
     (malyon-fatal-error "unspecified internal runtime error."))))

(defun malyon-wait-char ()
  "Store the input character in a variable and resume execution."
  (interactive)
  (condition-case nil
      (progn
  (malyon-store-variable 
   (malyon-read-code-byte)
   (malyon-char-to-int (malyon-unicode-to-zscii last-command-char)))
  (use-local-map malyon-keymap-read)
  (malyon-interpreter))
    (error
     (malyon-fatal-error "unspecified internal runtime error."))))

(defun malyon-history-previous-char (arg)
  "Display the previous item in the input history."
  (interactive "p")
  (let ((input (malyon-history-previous)))
    (cond ((> malyon-aread-beginning-of-line (point))
     (funcall malyon-history-saved-up arg))
    (input
     (save-excursion
       (set-buffer malyon-transcript-buffer)
       (delete-region malyon-aread-beginning-of-line (point-max)))
     (goto-char (point-max))
     (insert input)
     (malyon-adjust-transcript)))))

(defun malyon-history-next-char (arg)
  "Display the next item in the input history."
  (interactive "p")
  (let ((input (malyon-history-next)))
    (cond ((> malyon-aread-beginning-of-line (point))
     (funcall malyon-history-saved-down arg))
    (input
     (save-excursion
       (set-buffer malyon-transcript-buffer)
       (delete-region malyon-aread-beginning-of-line (point-max)))
     (goto-char (point-max))
     (insert input)
     (malyon-adjust-transcript)))))

(defun malyon-beginning-of-line (arg)
  "Go to the beginning of the line."
  (interactive "p")
  (if (> malyon-aread-beginning-of-line (point))
      (beginning-of-line)
    (goto-char malyon-aread-beginning-of-line)))

(defun malyon-kill-region (arg)
  "Kill region."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (kill-region (point) (mark))
    (message "Editing is restricted to the input prompt.")))

(defun malyon-kill-line (arg)
  "Kill rest of the current line."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (kill-line)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-kill-word (arg)
  "Kill the current word."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (kill-word 1)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-yank (arg)
  "Yank."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (yank)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-yank-pop (arg)
  "Yank pop."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (yank-pop 1)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-delete-char (arg)
  "Delete a character."
  (interactive "p")
  (if (<= malyon-aread-beginning-of-line (point))
      (delete-char 1)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-backward-delete-char (arg)
  "Delete a character backwards."
  (interactive "p")
  (if (< malyon-aread-beginning-of-line (point))
      (backward-delete-char-untabify 1)
    (message "Editing is restricted to the input prompt.")))

(defun malyon-self-insert-command (arg)
  "Insert a character."
  (interactive "p")
  (if (> malyon-aread-beginning-of-line (point))
      (goto-char (point-max)))
  (self-insert-command 1))

;; tracing utility

(defun malyon-trace-file ()
  "Turn tracing on for a particular file."
  (let ((trace
   (get-buffer-create
    (concat "Malyon Trace " malyon-story-file-name))))
    (if trace
  (save-excursion
    (set-buffer trace)
    (malyon-erase-buffer)
    (insert (concat "Tracing " malyon-story-file-name "..."))
    (newline)))))
  
(defun malyon-trace-newline ()
  "Output tracing newline."
  (let ((trace (get-buffer (concat "Malyon Trace " malyon-story-file-name))))
    (if trace
  (save-excursion
    (set-buffer trace)
    (goto-char (point-max))
    (newline)))))

(defun malyon-trace-opcode (pc opcode operands)
  "Output a z code instruction."
  (malyon-trace-string
   (format "%8d   %-3d %-25s %s\n"
     pc
     opcode
     (symbol-name (aref malyon-opcodes opcode))
     (apply 'concat (malyon-mapcan
         (lambda (x)
           (list " "
           (number-to-string
            (if (malyon-characterp x)
          (malyon-char-to-int x)
              x))))
         operands)))))

(defun malyon-trace-string (s)
  "Output tracing string."
  (let ((trace (get-buffer (concat "Malyon Trace " malyon-story-file-name))))
    (if (and trace s)
  (save-excursion
    (set-buffer trace)
    (goto-char (point-max))
    (insert s)))))

(defun malyon-trace-object (o)
  "Output tracing object."
  (let ((trace (get-buffer (concat "Malyon Trace " malyon-story-file-name))))
    (if (and trace o)
  (save-excursion
    (set-buffer trace)
    (goto-char (point-max))
    (prin1 o trace)))))

;;; announce malyon-mode

(provide 'malyon-mode)
(provide 'malyon)

;;; malyon-mode.el ends here
