;;; ez.el --- Emacs Z-machine ;; Copyright (C) 2021,2022,2023 Tim Vaughan ;; Author: Tim Vaughan ;; Created: 13 Oct 2021 ;; Version: 1.0 ;; Keywords: game ;; Homepage: http://thelambdalab.xyz/ez ;; Package-Requires: ((emacs "26")) ;; This file is not part of GNU Emacs. ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this file. If not, see . ;;; Commentary: ;; There are many Z-machine interpreters, but this one is mine. ;; It only capable of interpreting the subset of Z-code necessary ;; to run the first Zork game in z3 format. ;; ;; It is based entirely on the description of the Z-machine contained ;; Marnix Klooster's wonderful document, "The Z-machine And How to Emulate It." ;;; Code: ;; Utility functions (defun ez-decode-signed-bits (n nbits) (if (= 0 (lsh n (- 1 nbits))) n (- n (lsh 1 nbits)))) (defun ez-decode-signed-byte (b) (ez-decode-signed-bits b 8)) (defun ez-decode-signed-word (w) (ez-decode-signed-bits w 16)) (defun ez-decode-signed-operand (operand operand-type) (if (eq operand-type 'b) (ez-decode-signed-byte operand) (ez-decode-signed-word operand))) (defun binformat (n &optional s) (unless s (setq s "")) (let ((d (/ n 2)) (new-s (concat (number-to-string (mod n 2)) s))) (if (= d 0) new-s (binformat d new-s)))) (defun ez-list-to-string-hex (l) (concat "(" (when l (concat (format "%x" (car l)) (apply 'concat (mapcar (lambda (n) (format " %x" n)) (cdr l))))) ")")) ;; Memory (defvar ez-memory nil "Memory of z-machine.") (defvar ez-version nil) (defvar ez-start-pc nil) (defvar ez-himem-base nil) (defvar ez-dict-base nil) (defvar ez-objtab-addr nil) (defvar ez-abbrevtab-addr nil) (defvar ez-globalvartab-addr nil) (defvar ez-dict-entries nil "Parsed dictionary") (defvar ez-dict-separators nil "Parsed dictionary") (defun ez-mem-ref-byte (addr) (aref ez-memory addr)) (defun ez-mem-ref-bytes (addr count) (mapcar (lambda (offset) (ez-mem-ref-byte (+ addr offset))) (number-sequence 0 (- count 1)))) (defun ez-mem-set-byte (addr val) (aset ez-memory addr val)) (defun ez-mem-set-bytes (addr vals) (dotimes (i (length vals)) (ez-mem-set-byte (+ addr i) (elt vals i)))) (defun ez-mem-ref-word (addr) (logior (lsh (aref ez-memory addr) 8) (aref ez-memory (+ addr 1)))) (defun ez-mem-ref-words (addr count) (mapcar (lambda (offset) (ez-mem-ref-word (+ addr offset))) (number-sequence 0 (- (* 2 count) 1) 2))) (defun ez-mem-set-word (addr val) (let ((byte-high (/ val 256)) (byte-low (mod val 256))) (aset ez-memory addr byte-high) (aset ez-memory (+ addr 1) byte-low))) (defun ez-mem-ref-string (addr1 addr2) (substring ez-memory addr1 addr2)) (defun ez-parse-header () (setq ez-version (ez-mem-ref-byte #x0)) (setq ez-himem-addr (ez-mem-ref-word #x4)) (setq ez-start-pc (ez-mem-ref-word #x6)) (setq ez-dict-base (ez-mem-ref-word #x8)) (setq ez-objtab-addr (ez-mem-ref-word #xA)) (setq ez-globalvartab-addr (ez-mem-ref-word #xC)) (setq ez-abbrevtab-addr (ez-mem-ref-word #x18))) (defun ez-load-file (filename) "Load story file into memory." (with-temp-buffer (insert-file-contents-literally filename) (setq ez-memory (encode-coding-string (buffer-string) 'raw-text))) 'done) ;; Global variables (defun ez-get-global-var (gvar) (if (> gvar 239) (error "Invalid global variable %d" gvar)) (let ((val (ez-mem-ref-word (+ (* 2 gvar) ez-globalvartab-addr)))) (ez-debug-message "\tRetrieved %x from global variable %x" val gvar) val)) (defun ez-set-global-var (gvar val) (ez-mem-set-word (+ (* 2 gvar) ez-globalvartab-addr) val) (ez-debug-message "\tSet global variabl %x to %x" gvar val)) ;; Object tree (defvar ez-property-defaults nil) (defun ez-load-property-defaults () (setq ez-property-defaults (make-vector 31 0)) (dotimes (i 31) (aset ez-property-defaults i (aref ez-memory (+ ez-objtab-addr (* 2 i)))))) (defun ez-get-obj-addr (obj) (+ ez-objtab-addr (* 2 31) (* 9 (- obj 1)))) (defun ez-get-obj-parent (obj) (let ((addr (ez-get-obj-addr obj))) (ez-mem-ref-byte (+ addr 4)))) (defun ez-set-obj-parent (obj new-parent) (let ((addr (ez-get-obj-addr obj))) (ez-mem-set-byte (+ addr 4) new-parent))) (defun ez-get-obj-sibling (obj) (let ((addr (ez-get-obj-addr obj))) (ez-mem-ref-byte (+ addr 5)))) (defun ez-set-obj-sibling (obj new-sibling) (let ((addr (ez-get-obj-addr obj))) (ez-mem-set-byte (+ addr 5) new-sibling))) (defun ez-get-obj-child (obj) (let ((addr (ez-get-obj-addr obj))) (ez-mem-ref-byte (+ addr 6)))) (defun ez-set-obj-child (obj new-child) (let ((addr (ez-get-obj-addr obj))) (ez-mem-set-byte (+ addr 6) new-child))) (defun ez-get-obj-plist-addr (obj) (let ((addr (ez-get-obj-addr obj))) (ez-mem-ref-word (+ addr 7)))) (defun ez-get-obj-name (obj) (let ((plist-addr (ez-get-obj-plist-addr obj))) (if (> (ez-mem-ref-byte plist-addr) 0) (ez-get-zstring (+ 1 (ez-get-obj-plist-addr obj))) nil))) (defun ez-get-prop-default (prop) (ez-mem-ref-word (+ ez-objtab-addr (* 2 (+ 1 prop))))) (defun ez-get-prop-len (prop-size-byte) (lsh prop-size-byte -5)) (defun ez-get-prop-num (prop-size-byte) (logand #b00011111 prop-size-byte)) (defun ez-get-obj-prop-addr (obj prop) (let* ((plist-addr (ez-get-obj-plist-addr obj)) (prop-addr (+ plist-addr 1 (* 2 (ez-mem-ref-byte plist-addr)))) (size-byte (ez-mem-ref-byte prop-addr))) (while (not (or (= size-byte 0) (= prop (ez-get-prop-num size-byte)))) (setq prop-addr (+ prop-addr (ez-get-prop-len size-byte) 2) size-byte (ez-mem-ref-byte prop-addr))) prop-addr)) (defun ez-get-obj-prop (obj prop) (let* ((prop-addr (ez-get-obj-prop-addr obj prop)) (size-byte (ez-mem-ref-byte prop-addr))) (if (= size-byte 0) (ez-get-prop-default prop) (let ((prop-len (ez-get-prop-len size-byte)) (data-addr (+ prop-addr 1))) (cond ((= prop-len 0) (ez-mem-ref-byte data-addr)) ((>= prop-len 1) (ez-mem-ref-word data-addr))))))) (defun ez-set-obj-prop (obj prop value) (let* ((prop-addr (ez-get-obj-prop-addr obj prop)) (size-byte (ez-mem-ref-byte prop-addr))) (cond ((= size-byte 0) (error "Tried to set non-existant property")) ((= (ez-get-prop-len size-byte) 1) (ez-mem-set-byte (+ prop-addr 1) value)) (t (ez-mem-set-word (+ prop-addr 1) value))))) (defun ez-get-obj-attr (obj attr) (let* ((byte-num (/ attr 8)) (bit-num (mod attr 8)) (bit-mask (lsh 1 (- 7 bit-num)))) (if (> (logand bit-mask (ez-mem-ref-byte (+ (ez-get-obj-addr obj) byte-num))) 0) 1 0))) (defun ez-set-obj-attr (obj attr val) (let* ((byte-num (/ attr 8)) (bit-num (mod attr 8)) (bit-mask (lsh 1 (- 7 bit-num))) (byte-addr (+ (ez-get-obj-addr obj) byte-num)) (byte (ez-mem-ref-byte byte-addr)) (current-set (> (logand bit-mask byte) 0))) (if (or (and current-set (= val 0)) (and (not current-set) (> val 0))) (ez-mem-set-byte byte-addr (logxor byte bit-mask))))) (defun ez-remove-obj (obj) (let ((parent (ez-get-obj-parent obj)) (sibling (ez-get-obj-sibling obj))) (unless (= parent 0) (let ((child (ez-get-obj-child parent))) (if (= child obj) (ez-set-obj-child parent sibling) (while (not (= obj (ez-get-obj-sibling child))) (setq child (ez-get-obj-sibling child))) (ez-set-obj-sibling child (ez-get-obj-sibling obj)))) (ez-set-obj-parent obj 0)) (ez-set-obj-sibling obj 0))) (defun ez-insert-obj (obj new-parent) (ez-remove-obj obj) (unless (= new-parent 0) (ez-set-obj-sibling obj (ez-get-obj-child new-parent)) (ez-set-obj-child new-parent obj))) ;; Z-strings (defvar ez-zstring-alphabets ; 1 1 2 2 3 ; 0 5 0 5 0 5 0 (list " abcdefghijklmnopqrstuvwxyz" " ABCDEFGHIJKLMNOPQRSTUVWXYZ" " \n0123456789.,!?_#'\"/\\-:()") "Alphabets used by V3") (defun ez-parse-zstring-word (word) (list (lsh word -15) (logand (lsh word -10) #b11111) (logand (lsh word -5) #b11111) (logand word #b11111))) (defun ez-make-zstring-word (zchar-triple) (let ((c1 (elt zchar-triple 0)) (c2 (elt zchar-triple 1)) (c3 (elz zchar-triple 2))) (logand ))) (defun ez-get-zstring-chars-and-length (base-addr) (let ((addr base-addr) (chars nil) (not-done t) (word-count 0)) (while not-done (let ((components (ez-parse-zstring-word (ez-mem-ref-word addr)))) (setq chars (append chars (cdr components))) (setq addr (+ addr 2)) (setq word-count (+ word-count 1)) (when (= (car components) 1) (setq not-done nil)))) (cons word-count chars))) (defun ez-get-zstring-and-length (base-addr) (let* ((word-count-and-chars (ez-get-zstring-chars-and-length base-addr)) (word-count (car word-count-and-chars)) (chars (cdr word-count-and-chars)) (cur 0) (lock 0) (s "")) (while (> (length chars) 0) (let ((char (pop chars))) (cond ((memq char '(1 2 3)) ;Abbreviation (let* ((abbrev-char char) (abbrev-addr (* 2 (ez-mem-ref-word (+ ez-abbrevtab-addr (* 2 (+ (* (- abbrev-char 1) 32) (pop chars)))))))) (setq s (concat s (cdr (ez-get-zstring-and-length abbrev-addr)))))) ((= char 4) (setq cur (mod (+ cur 1) 3))) ((= char 5) (setq cur (mod (+ 3 (- cur 1)) 3))) ((and (= cur 2) (= char 6)) (setq s (concat s (char-to-string (+ (lsh (pop chars) 5) (pop chars))))) (setq cur lock)) (t (setq s (concat s (substring (elt ez-zstring-alphabets cur) char (+ char 1)))) (setq cur lock))))) (cons word-count s))) (defun ez-get-zstring (base-addr) (cdr (ez-get-zstring-and-length base-addr))) (defun ez-encode (string) "Encodes a string as a 2-word 5-padded Z-string. Used for matching input with dictionary entries when tokenizing. Returns a length-2 list of words for comparison with a dictionary entry." (let ((chars (mapcan (lambda (c) (cond ((seq-contains-p (elt ez-zstring-alphabets 0) c) (list (seq-position (elt ez-zstring-alphabets 0) c))) ((seq-contains-p (elt ez-zstring-alphabets 1) c) (list 4 (seq-position (elt ez-zstring-alphabets 1) c))) ((seq-contains-p (elt ez-zstring-alphabets 2) c) (list 5 (seq-position (elt ez-zstring-alphabets 2) c))) (t (list 5 6 (lsh c -5) (logand c #b11111))))) string))) (let* ((zstring-chars (if (< (length chars) 6) (append chars (make-list (- 6 (length chars)) 5)) (take 6 chars))) (c1 (elt zstring-chars 0)) (c2 (elt zstring-chars 1)) (c3 (elt zstring-chars 2)) (c4 (elt zstring-chars 3)) (c5 (elt zstring-chars 4)) (c6 (elt zstring-chars 5))) (list (logior (lsh c1 10) (lsh c2 5) c3) (logior (lsh 1 15) (lsh c4 10) (lsh c5 5) c6))))) ;; Dictionary (defun ez-parse-dictionary-header () (let* ((nseps (ez-mem-ref-byte ez-dict-base)) (separators (mapcar (lambda (i) (ez-mem-ref-byte (+ ez-dict-base i))) (number-sequence 1 nseps))) (bytes-per-entry (ez-mem-ref-byte (+ ez-dict-base 1 nseps))) (nentries (ez-mem-ref-word (+ ez-dict-base 2 nseps))) (entries-base (+ ez-dict-base nseps 4)) (entries nil)) ;; (dotimes (i nentries) ;; (let ((this-base (+ entries-base (* bytes-per-entry i)))) ;; (setq entries (cons (cons (ez-get-zstring this-base) ;; this-base) ;; entries)))) ;; (setq ez-dict-entries (reverse entries)) (setq ez-dict-separators separators))) (defun ez-is-separator (char) (let* ((nseps (ez-mem-ref-byte ez-dict-base))) (while (and (> nseps 0) (not (= (ez-mem-ref-byte (+ ez-dict-base nseps)) char))) (setq nseps (- nseps 1))) (> nseps 0))) (defun ez-lookup-dictionary (text) (let* ((encoded-text (ez-encode text)) (nseps (ez-mem-ref-byte ez-dict-base)) (bytes-per-entry (ez-mem-ref-byte (+ ez-dict-base 1 nseps))) (nentries (ez-mem-ref-word (+ ez-dict-base 2 nseps))) (entries-seen 0) (this-entry (+ ez-dict-base nseps 4))) (while (and (< entries-seen nentries) (not (equal (ez-mem-ref-words this-entry 2) encoded-text))) (setq entries-seen (+ entries-seen 1)) (setq this-entry (+ this-entry bytes-per-entry))) (if (< entries-seen nentries) this-entry 0))) ;; Call stack (defvar ez-call-stack nil) (defun ez-make-call-stack-frame (pc &optional call-method) (list pc nil (make-vector 15 0) call-method)) (defun ez-add-call-stack-frame (pc &optional call-method) (push (ez-make-call-stack-frame pc call-method) ez-call-stack)) (defun ez-pop-call-stack-frame () (pop ez-call-stack)) (defun ez-routine-stack () (elt (car ez-call-stack) 1)) (defun ez-routine-stack-push (val) (let ((frame (car ez-call-stack))) (ez-debug-message "\tPushed %x to stack" val) (setf (elt frame 1) (cons val (elt frame 1))))) (defun ez-routine-stack-pop () (let* ((frame (car ez-call-stack)) (rs-head (car (elt frame 1)))) (setf (elt frame 1) (cdr (elt frame 1))) (ez-debug-message "\tPopped %x from stack" rs-head) rs-head)) (defun ez-get-local-var (lvar) (let* ((frame (car ez-call-stack)) (val (aref (elt frame 2) (- lvar 1)))) (ez-debug-message "\tRetrieved value %x from local variable %x" val lvar) val)) (defun ez-set-local-var (lvar val) (let ((frame (car ez-call-stack))) (ez-debug-message "\tSet local variable %x to %x" lvar val) (aset (elt frame 2) (- lvar 1) val))) (defun ez-get-pc () (caar ez-call-stack)) (defun ez-set-pc (new-pc) (setf (car (car ez-call-stack)) new-pc)) (defun ez-increment-pc (inc) (ez-set-pc (+ (ez-get-pc) inc))) (defun ez-read-pc-byte-and-inc () (let ((res (ez-mem-ref-byte (ez-get-pc)))) (ez-increment-pc 1) res)) (defun ez-read-pc-word-and-inc () (let ((res (ez-mem-ref-word (ez-get-pc)))) (ez-increment-pc 2) res)) ;; Instruction execution (defun ez-get-var (var) (cond ((= var 0) (ez-routine-stack-pop)) ((< var 16) (ez-get-local-var var)) (t (ez-get-global-var (- var 16))))) (defun ez-set-var (var val) (cond ((= var 0) (ez-routine-stack-push val)) ((< var 16) (ez-set-local-var var val)) (t (ez-set-global-var (- var 16) val)))) (defun ez-read-pc-var-and-inc () (ez-get-var (ez-read-pc-byte-and-inc))) (defun ez-execute-instr () (let ((instr-pc (ez-get-pc)) (opbyte (ez-read-pc-byte-and-inc)) (optype) (opcode) (operands)) (cond ((<= #x0 opbyte #x1f) (setq optype '2op opcode opbyte operands (list (ez-read-pc-byte-and-inc) (ez-read-pc-byte-and-inc)) operand-types '(b b))) ((<= #x20 opbyte #x3F) (setq optype '2op opcode (- opbyte #x20) operands (list (ez-read-pc-byte-and-inc) (ez-read-pc-var-and-inc)) operand-types '(b w))) ((<= #x40 opbyte #x5F) (setq optype '2op opcode (- opbyte #x40) operands (list (ez-read-pc-var-and-inc) (ez-read-pc-byte-and-inc)) operand-types '(w b))) ((<= #x60 opbyte #x7F) (setq optype '2op opcode (- opbyte #x60) operands (list (ez-read-pc-var-and-inc) (ez-read-pc-var-and-inc)) operand-types '(w w))) ((<= #x80 opbyte #x8F) (setq optype '1op opcode (- opbyte #x80) operands (list (ez-read-pc-word-and-inc)) operand-types '(w))) ((<= #x90 opbyte #x9F) (setq optype '1op opcode (- opbyte #x90) operands (list (ez-read-pc-byte-and-inc)) operand-types '(b))) ((<= #xA0 opbyte #xAF) (setq optype '1op opcode (- opbyte #xa0) operands (list (ez-read-pc-var-and-inc)) operand-types '(w))) ((<= #xB0 opbyte #xBF) (setq optype '0op opcode (- opbyte #xb0) operands '() operand-types '())) ((<= #xC0 opbyte #xDF) (setq optype '2op opcode (- opbyte #xc0)) (let ((operands-and-types (ez-read-var-operands-and-inc))) (setq operands (car operands-and-types) operand-types (cdr operands-and-types)))) ((<= #xE0 opbyte #xFF) (setq optype 'var opcode (- opbyte #xe0)) (let ((operands-and-types (ez-read-var-operands-and-inc))) (setq operands (car operands-and-types) operand-types (cdr operands-and-types))))) (let ((table-row (assoc (list optype opcode) ez-op-table))) (unless table-row (error "Unsupported op PC:%x Optype:%s Opcode:%x Operands:%s Operand-types:%s" instr-pc optype opcode (ez-list-to-string-hex operands) operand-types)) (let ((mnemonic (elt table-row 1))) (ez-debug-message "PC:%x Optype:%s Opcode:%x Mnemonic:%s Operands:%s Operand-types:%s" instr-pc optype opcode mnemonic (ez-list-to-string-hex operands) operand-types)) (funcall (elt table-row 2) operands operand-types)))) (defun ez-read-var-operands-and-inc () (let* ((type-byte (ez-read-pc-byte-and-inc)) (types (let ((type1 (lsh type-byte -6))) (if (= type1 #b11) nil (cons type1 (let ((type2 (mod (lsh type-byte -4) 4))) (if (= type2 #b11) nil (cons type2 (let ((type3 (mod (lsh type-byte -2) 4))) (if (= type3 #b11) nil (cons type3 (let ((type4 (mod type-byte 4))) (if (= type4 #b11) nil (list type4)))))))))))))) (cons (mapcar (lambda (type) (cond ((= type 0) (ez-read-pc-word-and-inc)) ((= type 1) (ez-read-pc-byte-and-inc)) ((= type 2) (ez-read-pc-var-and-inc)))) types) (mapcar (lambda (type) (if (= type 1) 'b 'w)) types)))) ;; Branches (defun ez-do-branch (branch) (let* ((branch-byte (ez-read-pc-byte-and-inc)) (invert (= 0 (logand branch-byte #b10000000))) (single-byte (> (logand branch-byte #b01000000) 0)) (offset (if single-byte (logand branch-byte #b00111111) (let ((pos (= (logand branch-byte #b00100000) 0)) (val (+ (* 256 (logand branch-byte #b00011111)) (ez-read-pc-byte-and-inc)))) (if pos val (- val 8192)))))) (if (or (and branch (not invert)) (and (not branch) invert)) (cond ((= offset 0) (ez-op-rfalse)) ((= offset 1) (ez-op-rtrue)) (t (ez-set-pc (+ (ez-get-pc) offset -2))))))) ;; Operations (defvar ez-op-table '(((0op #x00) rtrue ez-op-rtrue) ((0op #x01) rfalse ez-op-rfalse) ((1op #x00) jz ez-op-jz) ((1op #x05) inc ez-op-inc) ((1op #x06) dec ez-op-dec) ((1op #x0B) ret ez-op-ret) ((0op #x08) ret_pulled ez-op-ret-pulled) ((1op #x0C) jump ez-op-jump) ((2op #x05) inc_jg ez-op-inc-jg) ((2op #x04) dec_jg ez-op-dec-jg) ((2op #x0D) store ez-op-store) ((1op #x0E) load ez-op-load) ((var #x01) storew ez-op-storew) ((2op #x0F) loadw ez-op-loadw) ((var #x02) storeb ez-op-storeb) ((2op #x10) loadb ez-op-loadb) ((2op #x01) je ez-op-je) ((2op #x02) jl ez-op-jl) ((2op #x03) jg ez-op-jg) ((2op #x06) jin ez-op-jin) ((2op #x07) test ez-op-test) ((2op #x08) or ez-op-or) ((2op #x09) and ez-op-and) ((2op #x14) add ez-op-add) ((2op #x15) sub ez-op-sub) ((2op #x16) mul ez-op-mul) ((2op #x17) div ez-op-div) ((2op #x18) mod ez-op-mod) ((var #x00) call_fv ez-op-callfv) ((1op #x01) get_sibling ez-op-get-sibling) ((1op #x02) get_child ez-op-get-child) ((1op #x03) get_parent ez-op-get-parent) ((2op #x0A) test_attr ez-op-test-attr) ((2op #x0B) set_attr ez-op-set-attr) ((2op #x0C) clear_attr ez-op-clear-attr) ((1op #x09) remove_obj ez-op-remove-obj) ((2op #x0E) insert_obj ez-op-insert-obj) ((var #x03) put_prop ez-op-put-prop) ((2op #x11) get_prop ez-op-get-prop) ((0op #x02) print ez-op-print) ((0op #x0B) new_line ez-op-new-line) ((var #x06) print_num ez-op-print-num) ((var #x05) print_char ez-op-print-char) ((1op #x0A) print_obj ez-op-print-obj) ((var #x04) read ez-op-read))) (defun ez-op-ret (operands &optional operand-types) (let ((retval (car operands))) (ez-debug-message "\tReturning value %x" retval) (ez-pop-call-stack-frame) (ez-set-var (ez-read-pc-byte-and-inc) retval)) 'run) (defun ez-op-ret-pulled (operands operand-types) (let ((retval (ez-routine-stack-pop))) (ez-debug-message "\tReturning value %x" retval) (ez-op-ret (list retval))) 'run) (defun ez-op-rtrue (&optional operands operand-types) (ez-op-ret (list 1)) 'run) (defun ez-op-rfalse (&optional operands operand-types) (ez-op-ret (list 0)) 'run) (defun ez-op-jz (operands operand-types) (ez-do-branch (= (car operands) 0)) 'run) (defun ez-op-je (operands operand-types) (ez-do-branch (memq (car operands) (cdr operands))) 'run) (defun ez-op-jg (operands operand-types) (let ((s1 (ez-decode-signed-operand (car operands) (car operand-types))) (s2 (ez-decode-signed-operand (cadr operands) (cadr operand-types)))) (ez-do-branch (> s1 s2))) 'run) (defun ez-op-jl (operands operand-types) (let ((s1 (ez-decode-signed-operand (car operands) (car operand-types))) (s2 (ez-decode-signed-operand (cadr operands) (cadr operand-types)))) (ez-do-branch (< s1 s2))) 'run) (defun ez-op-inc-jg (operands operand-types) (let ((var (car operands))) (ez-op-inc (list var)) (ez-op-jg (cons (ez-get-var var) (cdr operands)) (cons 'w (cdr operand-types)))) 'run) (defun ez-op-dec-jl (operands operand-types) (let ((var (car operands))) (ez-op-dec (list var)) (ez-op-jl (cons (ez-get-var var) (cdr operands)) (cons 'w (cdr operand-types)))) 'run) (defun ez-op-jin (operands operand-types) (let ((obj (car operands)) (n (cadr operands))) (ez-do-branch (or (= n 0) (= n (ez-get-obj-parent obj))))) 'run) (defun ez-op-test (operands operand-types) (let ((a (car operands)) (b (cadr operands))) (ez-do-branch (= (logand a b) b))) 'run) (defun ez-op-jump (operands operand-types) (let ((offset (if (eq (car operand-types) 'b) (ez-decode-signed-byte (car operands)) (ez-decode-signed-word (car operands))))) (ez-set-pc (+ (ez-get-pc) offset -2))) 'run) (defun ez-op-inc (operands &optional operand-types) (let ((var (car operands))) (ez-set-var var (mod (+ 1 (ez-get-var var)) #x10000))) 'run) (defun ez-op-dec (operands &optional operand-types) (let ((var (car operands))) (ez-set-var var (mod (+ (ez-get-var var) 1) #x10000))) 'run) (defun ez-op-store (operands operand-types) (let ((var (car operands)) (a (cadr operands))) (ez-set-var var a)) 'run) (defun ez-op-load (operands operand-types) (let ((var (car operands))) (ez-set-var (ez-read-pc-byte-and-inc) (ez-get-var var))) 'run) (defun ez-op-storew (operands operand-types) (let ((baddr (car operands)) (n (cadr operands)) (a (caddr operands))) (ez-mem-set-word (+ baddr (* 2 n)) a)) 'run) (defun ez-op-loadw (operands operand-types) (let ((baddr (car operands)) (n (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (ez-mem-ref-word (+ baddr (* 2 n))))) 'run) (defun ez-op-storeb (operands operand-types) (let ((baddr (car operands)) (n (cadr operands)) (a (caddr operands))) (ez-mem-set-byte (+ baddr n) a)) 'run) (defun ez-op-loadb (operands operand-types) (let ((baddr (car operands)) (n (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (ez-mem-ref-byte (+ baddr n)))) 'run) (defun ez-op-and (operands operand-types) (let ((a (car operands)) (b (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (logand a b))) 'run) (defun ez-op-or (operands operand-types) (let ((a (car operands)) (b (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (logior a b))) 'run) (defun ez-op-add (operands operand-types) (let ((a (car operands)) (b (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (mod (+ a b) #x10000))) 'run) (defun ez-op-sub (operands operand-types) (let ((a (car operands)) (b (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (mod (+ (- a b) #x10000) #x10000))) 'run) (defun ez-op-mul (a b) (let ((a (car operands)) (b (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (mod (* a b) #x10000))) 'run) (defun ez-op-div (a b) (error "Not implemented")) (defun ez-op-mod (a b) (error "Not implemented")) (defun ez-op-callfv (operands operand-types) (let* ((raddr (car operands)) (call-operands (cdr operands)) (r (* 2 raddr)) (L (ez-mem-ref-byte r)) (n (length call-operands)) (new-pc (+ r 1 (* L 2)))) (if (= raddr 0) (ez-set-var (ez-read-pc-byte-and-inc) 0) ; Simply return 0 (ez-add-call-stack-frame new-pc) (dotimes (i L) (if (< i n) (ez-set-local-var (+ i 1) (elt call-operands i)) (ez-set-local-var (+ i 1) (ez-mem-ref-word (+ r 1 (* 2 i)))))))) 'run) (defun ez-op-test-attr (operands operand-types) (let ((obj (car operands)) (attr (cadr operands))) (ez-do-branch (= 1 (ez-get-obj-attr obj attr))) 'run)) (defun ez-op-set-attr (operands operand-types) (let ((obj (car operands)) (attr (cadr operands))) (ez-set-obj-attr obj attr 1)) 'run) (defun ez-op-clear-attr (operands operand-types) (let ((obj (car operands)) (attr (cadr operands))) (ez-set-obj-attr obj attr 0)) 'run) (defun ez-op-get-sibling (operands operand-types) (let ((sib (ez-get-obj-sibling (car operands)))) (ez-set-var (ez-read-pc-byte-and-inc) sib) (ez-do-branch (> sib 0))) 'run) (defun ez-op-get-child (operands operand-types) (let ((child (ez-get-obj-child (car operands)))) (ez-set-var (ez-read-pc-byte-and-inc) child) (ez-do-branch (> child 0))) 'run) (defun ez-op-get-parent (operands operand-types) (let ((parent (ez-get-obj-parent (car operands)))) (ez-set-var (ez-read-pc-byte-and-inc) parent)) 'run) (defun ez-op-remove-obj (operands operand-types) (let ((obj (car operands))) (ez-remove-obj obj)) 'run) (defun ez-op-insert-obj (operands operand-types) (let ((obj1 (car operands)) (obj2 (cadr operands))) (ez-insert-obj obj1 obj2)) 'run) (defun ez-op-put-prop (operands operand-types) (let* ((obj (car operands)) (prop (cadr operands)) (a (caddr operands))) (ez-set-obj-prop obj prop a)) 'run) (defun ez-op-get-prop (operands operand-types) (let* ((obj (car operands)) (prop (cadr operands))) (ez-set-var (ez-read-pc-byte-and-inc) (ez-get-obj-prop obj prop))) 'run) (defun ez-op-print (operands operand-types) (let* ((word-count-and-string (ez-get-zstring-and-length (ez-get-pc))) (word-count (car word-count-and-string)) (string (cdr word-count-and-string))) (ez-print string) (ez-increment-pc (* 2 word-count))) 'run) (defun ez-op-new-line (operands operand-types) (ez-print "\n") 'run) (defun ez-op-print-num (operands operand-types) (let ((s (ez-decode-signed-operand (car operands) (car operand-types)))) (ez-print (number-to-string s))) 'run) (defun ez-op-print-char (operands operand-types) (let ((c (car operands))) (ez-print (string c))) 'run) (defun ez-op-print-obj (operands operand-types) (let ((obj (car operands))) (ez-print (ez-get-obj-name obj))) 'run) (defvar ez--next-read-args nil) (defun ez-op-read (operands operand-types) (let ((baddr1 (car operands)) (baddr2 (cadr operands))) (setq ez--next-read-args (list baddr1 baddr2))) 'wait-for-input) (defun ez-op-read2 (input-string) (let* ((baddr1 (car ez--next-read-args)) (baddr2 (cadr ez--next-read-args))) (dotimes (i (length input-string)) (let ((char (elt input-string i))) (ez-mem-set-byte (+ baddr1 1 i) char))) (ez-mem-set-byte (+ baddr1 1 (length input-string)) 0) (ez--tokenize baddr1 baddr2)) 'run) (defun ez--tokenize (tb-baddr pb-baddr) (let ((unfinished t) (token-start 0) (token-end 0) (token-count 0)) (while unfinished (let ((next-char (ez-mem-ref-byte (+ tb-baddr 1 token-end)))) (cond ((eq next-char ?\s) ;; Add token (setq token-end (- token-end 1)) (let* ((text (ez-mem-ref-string (+ tb-baddr 1 token-start) (+ tb-baddr 1 token-end))) (dict-entry (ez-lookup-dictionary text))) (setq token-count (+ token-count 1)) (ez-mem-set-word (+ pb-baddr 2 (* token-count 4)) dict-entry) (ez-mem-set-bytes (+ pb-baddr 2 (* token-count 4) 2) (length text) token-start)) (setq token-start (+ token-end 1)) (setq token-end token-start)) ((ez-is-separator char) ;; Add token and separator token ) ((eq char 0) (setq unfinished nil)) (setq token-end (+ token-end 1))) )))) ;; Execution loop (defun ez-load-and-run (filename) (ez-load-file filename) (ez-parse-header) (setq ez-call-stack (list (ez-make-call-stack-frame ez-start-pc))) (ez-run)) (defvar ez-machine-state nil "Identifies the current executation state of the Z-machine.") (defun ez-run () (setq ez-machine-state 'run) (while (eq ez-machine-state 'run) (setq ez-machine-state (ez-execute-instr)))) ;;; Buffer and I/O ;; (defvar ez-input-marker nil "Marker for input position in buffer.") (defun ez-setup-buffer () (with-current-buffer (get-buffer-create "*ez*") (ez-mode) (let ((inhibit-read-only t)) (erase-buffer)) (setq-local scroll-conservatively 1) (if (markerp ez-input-marker) (set-marker ez-input-marker (point-max)) (setq ez-input-marker (point-max-marker))) (goto-char (point-max)))) (defun ez-print (string) (with-current-buffer "*ez*" (save-excursion (goto-char ez-input-marker) (insert-before-markers string)))) (defun ez-enter () (interactive) (if (not (eq ez-machine-state 'wait-for-input)) (error "Z-machine not ready for input.")) (let ((input-string (downcase (with-current-buffer "*ez*" (buffer-substring ez-input-marker (point-max)))))) (delete-region ez-input-marker (point-max)) (ez-print (concat input-string "\n")) (ez-debug-message "\tReceived string \"%s\"" input-string) (ez-op-read2 input-string) (ez-run))) ;; Debugging info (defun ez-setup-debug-buffer () (with-current-buffer (get-buffer-create "*ez-debug-trace*") (setq-local buffer-read-only t) (let ((inhibit-read-only t)) (erase-buffer) (insert "--- Trace Start ---\n\n")))) (defun ez-debug-message (&rest strings) (with-current-buffer (get-buffer-create "*ez-debug-trace*") (save-excursion (goto-char (point-max)) (let ((inhibit-read-only t)) (insert (apply #'format-message strings) "\n"))))) (defun ez-debug-memory () (interactive) (with-current-buffer (get-buffer-create "*ez-debug-memory*") (setq-local buffer-read-only t) (let ((inhibit-read-only t) (old-point (point))) (if (eq major-mode 'hexl-mode) (hexl-mode-exit)) (erase-buffer) (insert ez-memory) (setq-local buffer-undo-list nil) (hexl-mode) (goto-char old-point)))) ;; Mode (defvar ez-mode-map (let ((map (make-sparse-keymap))) (define-key map (kbd "RET") 'ez-enter) map)) (define-derived-mode ez-mode text-mode "ez" "Major mode for EZ.") (when (fboundp 'evil-set-initial-state) (evil-set-initial-state 'ez-mode 'insert)) (defun ez (zfile) (interactive "fEnter name of z3 story file: ") (if (get-buffer "*ez*") (switch-to-buffer "*ez*") (switch-to-buffer "*ez*") (ez-setup-buffer) (ez-load-and-run zfile)) "Started EZ.") (defun ez-debug () (interactive) (ez-setup-buffer) (ez-setup-debug-buffer) (ez-load-and-run "zork1.z3")) ;;; ez.el ends here