153 lines
6.1 KiB
Common Lisp
153 lines
6.1 KiB
Common Lisp
(in-package #:user-side-compiler)
|
|
|
|
(defgeneric check-arguments (instruction))
|
|
|
|
(defclass instruction ()
|
|
((%mnemonic :allocation :class :reader mnemonic :initarg :mnemonic)
|
|
(%operand :accessor operand :initarg :operand)
|
|
(%next :accessor next :accessor normal-next :initarg :next)
|
|
(%source :accessor source :initarg :source :initform nil)))
|
|
|
|
(defmethod print-object ((object instruction) stream)
|
|
(cond ((some (lambda (type)
|
|
(typep object type))
|
|
'(immediate-mixin accumulator-mixin zero-page-mixin absolute-mixin))
|
|
(format stream "#<~A ~A~A~A>"
|
|
(mnemonic object)
|
|
(typecase object
|
|
(immediate-mixin "#")
|
|
((member indirect-y-mixin indirect-x-mixin) "(")
|
|
(t ""))
|
|
(typecase object
|
|
(accumulator-mixin "A")
|
|
(t (operand object)))
|
|
(typecase object
|
|
(indirect-x-mixin ", X)")
|
|
(indirect-y-mixin "),Y")
|
|
(t ""))))
|
|
(t
|
|
(format stream "#<~A~A>" (mnemonic object)
|
|
(if (or (typep object 'implied-mixin)
|
|
(typep object 'accumulator-mixin))
|
|
""
|
|
(format nil " ~S" (operand object)))))))
|
|
|
|
(defclass complete-instruction-mixin ()
|
|
((%opcode :allocation :class :reader opcode :initarg :opcode)))
|
|
|
|
(defmethod bytesquash ((object complete-instruction-mixin))
|
|
(append (list (opcode object))
|
|
(when (> (instruction-length object) 1)
|
|
(bytesquash (operand object)))))
|
|
|
|
(defclass immediate-mixin () ())
|
|
(defclass implied-mixin () ())
|
|
(defclass accumulator-mixin () ())
|
|
(defclass zero-page-mixin () ())
|
|
|
|
(defmethod shared-initialize :after ((instance zero-page-mixin) slot-names &rest initargs &key &allow-other-keys)
|
|
(declare (ignore slot-names initargs))
|
|
(when (numberp (operand instance))
|
|
(assert (< (operand instance) #x100))))
|
|
|
|
(defclass zero-page-x-mixin (zero-page-mixin) ())
|
|
(defclass absolute-mixin () ())
|
|
(defclass absolute-x-mixin (absolute-mixin) ())
|
|
(defclass absolute-y-mixin (absolute-mixin) ())
|
|
(defclass indirect-x-mixin (zero-page-mixin) ())
|
|
(defclass indirect-y-mixin (zero-page-mixin) ())
|
|
|
|
(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(defun addressing-mode-to-class-name (mode-keyword)
|
|
(cadr (assoc mode-keyword
|
|
'((:implied implied-mixin)
|
|
(:immediate immediate-mixin)
|
|
(:accumulator accumulator-mixin)
|
|
(:zero-page zero-page-mixin)
|
|
(:zero-page-x zero-page-x-mixin)
|
|
(:zero-page-y zero-page-y-mixin)
|
|
(:relative relative-mixin)
|
|
(:absolute absolute-mixin)
|
|
(:absolute-x absolute-x-mixin)
|
|
(:absolute-y absolute-y-mixin)
|
|
(:indirect-x indirect-x-mixin)
|
|
(:indirect-y indirect-y-mixin))))))
|
|
|
|
(defgeneric instruction-length (object)
|
|
(:method ((obj immediate-mixin)) 2)
|
|
(:method ((obj implied-mixin)) 1)
|
|
(:method ((obj accumulator-mixin)) 1)
|
|
(:method ((obj zero-page-mixin)) 2)
|
|
(:method ((obj relative-mixin)) 2)
|
|
(:method ((obj absolute-mixin)) 3))
|
|
|
|
(defclass branching-mixin ()
|
|
((%branch-next :accessor branch-next :initarg :branch-next)))
|
|
|
|
(defmacro define-instruction (mnemonic branching-p &rest modes-and-codes)
|
|
(let* ((name (string-upcase mnemonic))
|
|
(base-name (intern (format nil "INST-~A" name))))
|
|
`(progn
|
|
(defclass ,base-name (instruction)
|
|
((%mnemonic :allocation :class :initform ',mnemonic)))
|
|
(setf (slot-value (make-instance ',base-name) '%mnemonic) ',mnemonic)
|
|
,@(loop :for (mode code) :in modes-and-codes
|
|
:for class-name := (intern (format nil "INST-~A-~A" name mode))
|
|
:collect `(progn
|
|
(defclass ,class-name
|
|
,(append
|
|
`(,base-name
|
|
,(addressing-mode-to-class-name mode)
|
|
complete-instruction-mixin)
|
|
(when branching-p
|
|
'(branching-mixin)))
|
|
((%opcode :allocation :class :initform ,code)))
|
|
(setf (slot-value (allocate-instance (find-class ',class-name)) '%opcode) ,code))))))
|
|
|
|
(defun fix-label-addresses-in-instruction-list (instruction-list origin)
|
|
(loop :with address := origin
|
|
:with cell := instruction-list
|
|
:for obj :in instruction-list
|
|
:if (typep obj 'label)
|
|
:do (setf (address obj) address)
|
|
:else
|
|
:do (when (typep obj 'instruction)
|
|
(incf address (instruction-length obj)))
|
|
:unless (null (rest cell))
|
|
:do (setf cell (rest cell))
|
|
:finally (setf (cdr cell)
|
|
(list (make-label :name "PROGRAM_END" :address address)))))
|
|
|
|
(defun bytesquash-instruction-list (instruction-list origin)
|
|
(let* ((end-label (find "PROGRAM_END" (remove-if-not #'labelp instruction-list)
|
|
:key #'name :test #'equalp))
|
|
(byte-vector (make-array (- (address end-label) origin)
|
|
:element-type '(unsigned-byte 8)
|
|
:fill-pointer 0)))
|
|
(loop :for thing :in instruction-list
|
|
:when (typep thing 'instruction)
|
|
:do (dolist (byte (bytesquash thing))
|
|
(vector-push byte byte-vector)))
|
|
byte-vector))
|
|
|
|
;;; Testing
|
|
|
|
(define-instruction "TXA" nil (:implied #x8a))
|
|
(define-instruction "PHA" nil (:implied #x48))
|
|
(define-instruction "PLA" nil (:implied #x68))
|
|
(define-instruction "TAX" nil (:implied #xaa))
|
|
(define-instruction "DEX" nil (:implied #xca))
|
|
(define-instruction "BNE" t (:relative #xd0))
|
|
(define-instruction "LDA" nil
|
|
(:immediate #xa9)
|
|
(:zero-page #xa5)
|
|
(:absolute #xad)
|
|
(:absolute-y #xb9))
|
|
(define-instruction "LDY" nil
|
|
(:immediate #xa0)
|
|
(:absolute #xac))
|
|
(define-instruction "STA" nil
|
|
(:absolute #x8d)
|
|
(:absolute-y #x99))
|
|
(define-instruction "JSR" nil
|
|
(:absolute #x20))
|