1;;;; pasm.el
2;;;;
3;;;;
4;;;; this is a simple major mode for working with parrot assembler
5;;;; (and, to a certain extent, parrot imcc) files.
6;;;;
7;;;; first off: this file is 'it works for me' quality, use at your
8;;;; own risk
9;;;;
10;;;; offerings:
11;;;;
12;;;; 1) highlighting for labels, comments and ops which modify program
13;;;; flow (if, bsr, jsr, etc.). I have intentionally kept the
14;;;; highlighting to a minimum (highlighitng loses its point when you
15;;;; highlight _everything_), however if someone wants i'll add in
16;;;; different highlighting levels ala cperl-mode.
17;;;;
18;;;; 2) simple indentation (but it kills tabs, which i think is a good
19;;;; thing so i'm not going to fix it (yes, it'm just justifying my
20;;;; laziness (i just realized that this is going to create a *lot* of
21;;;; whitespace diffs ... hmmm ...)))
22;;;;
23;;;; 3) a simple function for following branches. see the doc string
24;;;; for pasm-follow-branch. By default this is bound to "C-c C-j".
25;;;;
26;;;; 4) a function for passing the current buffer to assembler.pl and
27;;;; passing the output of that to the parrot interpreter and putting
28;;;; the output in another window. see the doc string for
29;;;; pasm-assemble-and-run-buffer (dont't forget to set PERL5LIB). By
30;;;; default this is bound to "C-c C-c".
31;;;;
32;;;; COPYRIGHT (C) 2002 Edward Marco Baringer. All Rights Reserved.
33;;;; This file is free software. It may be used, redistributed
34;;;; and/or modified under the terms of the Perl Artistic License 2.0
35;;;; (see http://www.perlfoundation.org/artistic_license_2_0)
36;;;;
37
38(defvar *pasm-mode-syntax-table* nil)
39
40(defvar *pasm-mode-keymap* nil)
41
42(defvar *pasm-labeled-branching-ops*
43  '("bsr" "branch" "jsr" "jump" "eq" "ne" "lt" "le" "gt" "ge" "if" "unless")
44  "All the pasm ops which jump to a particular label, in other words
45all the ops which can change control flow minus 'ret' and 'end'")
46
47(defvar *pasm-label-regexp* "[A-Za-z_][A-Za-z_0-9]*:")
48
49(defvar *pasm-assembler-path* nil
50  "The path of the assembler. This will be passed as is to your shell,
51so either assembler.pl is in PATH or you need to use an absolute name")
52
53(defvar *pasm-parrot-path* nil
54  "The path of the parrot interpreter. As in *pasm-assembler-path*
55this will be passed as is to your shell (via shell-command-on-region's
56third arg) and so unless parrot in your PATH this should be an
57absolute path")
58
59(unless *pasm-mode-syntax-table*
60  (setq *pasm-mode-syntax-table* (make-syntax-table))
61  (modify-syntax-entry ?\\ "\\" *pasm-mode-syntax-table*)
62  (modify-syntax-entry ?#  "<"  *pasm-mode-syntax-table*)
63  (modify-syntax-entry ?\n ">"  *pasm-mode-syntax-table*)
64  (modify-syntax-entry ?:  "_"  *pasm-mode-syntax-table*))
65
66(unless *pasm-mode-keymap*
67  ;; please, someone tell how i should really do this...
68  (let ((inner-keymap (make-sparse-keymap)))
69    (define-key inner-keymap (kbd "C-c") 'pasm-assemble-and-run-buffer)
70    (define-key inner-keymap (kbd "C-j") 'pasm-follow-branch)
71    (setq *pasm-mode-keymap* (make-sparse-keymap))
72    (define-key *pasm-mode-keymap* (kbd "\C-c") inner-keymap)
73    (define-key *pasm-mode-keymap* (kbd "TAB") 'pasm-indent-function)))
74
75(setq pasm-font-lock-keywords `(;; labels
76                                ;; NB: i hearby decree that labels
77                                ;; must be the first thing on a line,
78                                ;; the assembler be damned.
79                                (,(concat "^" *pasm-label-regexp*) . font-lock-constant-face)
80                                ;; assembler directives
81                                ("^\\s-*\\.[a-zA-Z]*" . font-lock-builtin-face)
82                                ;; ops that jump: bsr, branch, jsr,
83                                ;; jump, eq, ne, lt, le, gt, ge, if,
84                                ;; unless, ret and goto
85                                ("\\<\\(b\\(sr\\|ranch\\)\\|e\\(q\\|nd\\)\\|g[te]\\|if\\|j\\(ump\\|sr\\)\\|l[te]\\|ne\\|ret\\|unless\\|goto\\)\\>" .
86                                 font-lock-keyword-face)
87                                ;; imcc temporary registers
88                                ("\\$\\(I\\|S\\|P\\|N\\)[0-9]+" .
89                                 font-lock-variable-name-face)
90                                ;; pasm registers
91                                ("\\<\\(I\\|S\\|P\\|N\\)\\([0-9]\\|[12][0-9]\\|3[01]\\)\\>" .
92                                 font-lock-variable-name-face)
93                                ;; basic types: int, string, pmc and float
94                                ("\\<\\(int\\|string\\|pmc\\|float\\)\\>" .
95                                 font-lock-type-face)))
96
97(defun pasm-mode ()
98  "Simple Emacs mode for editing Parrot Assembler"
99  (interactive)
100  (kill-all-local-variables)
101  (setq major-mode 'pasm-mode)
102  (setq mode-name "PASM")
103  (set-syntax-table *pasm-mode-syntax-table*)
104  (make-local-variable 'paragraph-start)
105  (setq paragraph-start (concat "^$\\|" page-delimiter))
106  (make-local-variable 'paragraph-separate)
107  (setq paragraph-separate paragraph-start)
108  (make-local-variable 'indent-line-function)
109  (setq indent-line-function 'pasm-indent-line-function)
110  (make-local-variable 'require-final-newline)
111  (setq require-final-newline t)
112  (make-local-variable 'comment-start)
113  (setq comment-start "# ")
114  (make-local-variable 'comment-end)
115  (setq comment-end "")
116  (make-local-variable 'comment-start-skip)
117  (setq comment-start-skip "#+ *")
118  (make-local-variable 'font-lock-defaults)
119  (setq font-lock-defaults '(pasm-font-lock-keywords))
120  (font-lock-mode 1)
121  (use-local-map *pasm-mode-keymap*)
122  (run-hooks 'pasm-mode-hook))
123
124(defun pasm-indent-line-function ()
125  (interactive)
126  (save-excursion
127    (beginning-of-line)
128    ;;(delete-region (point) (+ (point) (current-indentation)))
129    (delete-horizontal-space)
130    (cond
131     ((looking-at "[A-Za-z_][A-Za-z_0-9]*:")
132      (indent-to 0))
133     (t
134      (indent-to 8)))))
135
136(defun beginning-of-line-point (&optional n)
137  "Return the point at the beginning of the current line. N gets
138passed to beginning-of-line if you want"
139  (save-excursion
140    (beginning-of-line n)
141    (point)))
142
143(defun pasm-indent-function ()
144  "This differs from pasm-indent-line-function in that if we end up at
145the beginning of a line (which doesn't have a label) we want to be
146moved forward to column 8"
147  (interactive)
148  (pasm-indent-line-function)
149  ;; how do we check if we're at the beginning of a line? there must
150  ;; be a function for this
151  (unless (or (looking-at "[A-Za-z_][A-Za-z_0-9]*:")
152              (/= (beginning-of-line-point) (point)))
153      (forward-char 8)))
154
155(defun pasm-assemble-and-run-buffer ()
156  "Pretend the current buffer is pasm code and send it to assemble.pl,
157send the output of that to parrot and send the output of that to the
158*Parrot* buffer.
159
160This relies on the variables *pasm-assembler-path* to find
161assembler.pl and *pasm-parrot-path* to find the parrot interpreter. If
162there are any args you want passed to the assembler or the parrot
163interpreter just append them to the respective variable. Note that to
164whatever value these vars have the string \" -- - \" will be appended.
165
166NB: You need to add <parrot-root-dir>/lib to your PERL5LIB var for
167this to work (or you need to be lucky (which i guess we could say
168about anything))."
169  (interactive)
170  (let ((max-mini-window-height 0))
171    (shell-command-on-region (point-min)
172                             (point-max)
173                             (concat *pasm-assembler-path* " -- - | "
174                                     *pasm-parrot-path* " -- -")
175                             (get-buffer-create "*Parrot output*")))
176  (let ((current-buffer (current-buffer)))
177    (switch-to-buffer-other-window (get-buffer "*Parrot output*"))
178    (switch-to-buffer-other-window current-buffer)))
179
180(defun pasm-follow-branch ()
181  "Look at the current op, it it's a branching op we jump to the
182proper label (assuming it exists). In order to determine whether the
183current op is branching or not we rely on the value of
184*pasm-labeled-branching-ops*"
185  (interactive)
186  (let ((jump-to-point nil))
187    ;; jump-to-point and the save-excursion are so that if we're not
188    ;; on a branching op line or if the op to jump to isn't defined we
189    ;; don't move the point around unnecessarily (this would be very
190    ;; confusing, trust me)
191    (save-excursion
192      (beginning-of-line)
193      (save-match-data
194        (when (looking-at (concat "^" *pasm-label-regexp*))
195          (search-forward ":")))
196      (let ((op (buffer-substring (1- (search-forward-regexp "[^ \t\n\r]"))
197                                  (1- (search-forward-regexp "[^a-z_]")))))
198        (when (member op *pasm-labeled-branching-ops*)
199          ;; the label to jump to is the last sequence of [a-zA-Z_0-9]+
200          ;; chars in this instruction
201          (end-of-line)
202          (search-backward-regexp "[^a-zA-Z_0-9]")
203          (forward-char)
204          (let ((label (buffer-substring (point) (1- (search-forward-regexp "[^a-zA-Z_0-9]")))))
205            ;; label is the name (minus the trailing ':') of the label
206            ;; to jump to
207            (beginning-of-buffer)
208            (setq jump-to-point (search-forward (concat label ":")))))))
209    (if jump-to-point
210      (goto-char jump-to-point)
211      (message "Not on branching op or undefined label"))))
212