Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

737 lines
29 KiB

  1. ;;; masm-mode.el --- MASM x86 and x64 assembly major mode -*- lexical-binding: t; -*-
  2. ;; This is free and unencumbered software released into the public domain.
  3. ;; Author: YiGeeker <zyfchinese@yeah.net>
  4. ;; Version: 1.0.0
  5. ;; Package-Version: 20200308.1450
  6. ;; Package-Commit: 626b9255c2bb967a53d1d50be0b98a1bcae3250c
  7. ;; Package-Requires: ((emacs "25.1"))
  8. ;; Keywords: languages
  9. ;; URL: https://github.com/YiGeeker/masm-mode
  10. ;; This file is NOT part of GNU Emacs.
  11. ;; This program is free software; you can redistribute it and/or
  12. ;; modify it under the terms of GNU General Public License.
  13. ;;; Commentary:
  14. ;; A major mode for editing MASM x86 and x64 assembly code. It
  15. ;; includes syntax highlighting, automatic comment indentation and
  16. ;; various build commands.
  17. ;; Notice: masm-mode will clobber Emacs's built-in asm-mode.
  18. ;;; Code:
  19. (defgroup masm nil
  20. "Options for `masm-mode'."
  21. :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
  22. :group 'languages)
  23. (defcustom masm-program-win64 t
  24. "Syntax for `masm-mode', t for Win64 and nil for Win32."
  25. :type '(choice (const :tag "Win64" t)
  26. (const :tag "Win32" nil))
  27. :group 'masm-mode)
  28. (defcustom masm-win32-compile-args '("/c" "/coff")
  29. "Default arguments for the ml.exe in `masm-mode'."
  30. :type '(repeat string)
  31. :group 'masm-mode)
  32. (defcustom masm-win32-link-args '("/subsystem:windows")
  33. "Default arguments for the Win32 link.exe in `masm-mode'."
  34. :type '(repeat string)
  35. :group 'masm-mode)
  36. (defcustom masm-win32-executable-path ""
  37. "Path for ml.exe."
  38. :type 'directory
  39. :group 'masm-mode)
  40. (defcustom masm-win32-include-path ()
  41. "Path for Win32 inc files in `masm-mode'."
  42. :type '(repeat directory)
  43. :group 'masm-mode)
  44. (defcustom masm-win32-library-path ()
  45. "Path for Win32 lib files in `masm-mode'."
  46. :type '(repeat directory)
  47. :group 'masm-mode)
  48. (defcustom masm-win64-compile-args '("/c")
  49. "Default arguments for the ml64.exe in `masm-mode'."
  50. :type '(repeat string)
  51. :group 'masm-mode)
  52. (defcustom masm-win64-link-args '("/subsystem:windows" "/machine:x64" "/entry:main")
  53. "Default arguments for the Win64 link.exe in `masm-mode'."
  54. :type '(repeat string)
  55. :group 'masm-mode)
  56. (defcustom masm-win64-executable-path ""
  57. "Path for ml64.exe."
  58. :type 'directory
  59. :group 'masm-mode)
  60. (defcustom masm-win64-include-path ()
  61. "Path for Win64 inc files in `masm-mode'."
  62. :type '(repeat directory)
  63. :group 'masm-mode)
  64. (defcustom masm-win64-library-path ()
  65. "Path for Win64 lib files in `masm-mode'."
  66. :type '(repeat directory)
  67. :group 'masm-mode)
  68. (defcustom masm-build-executable "nmake"
  69. "Default executable for building the assembly project in `masm-mode'."
  70. :type 'string
  71. :group 'masm-mode)
  72. (defcustom masm-build-args ()
  73. "Default arguments for the build command in `masm-mode'."
  74. :type '(repeat string)
  75. :group 'masm-mode)
  76. (defvar-local masm--program-win64
  77. masm-program-win64
  78. "Decided by customizable value.")
  79. (defvar-local masm--compile-command-used nil
  80. "Save changed compile command.")
  81. (defvar-local masm--link-command-used nil
  82. "Save changed link command.")
  83. (defvar-local masm--build-command-used nil
  84. "Save changed build command.")
  85. (defgroup masm-mode-faces ()
  86. "Faces used by `masm-mode'."
  87. :group 'masm-mode)
  88. (defvar masm-mode-abbrev-table nil
  89. "Abbrev table used while in `masm-mode'.")
  90. (define-abbrev-table 'masm-mode-abbrev-table ())
  91. (defface masm-registers
  92. '((t :inherit (font-lock-variable-name-face)))
  93. "Face for registers."
  94. :group 'masm-mode-faces)
  95. (defface masm-prefix
  96. '((t :inherit (font-lock-builtin-face)))
  97. "Face for prefix."
  98. :group 'masm-mode-faces)
  99. (defface masm-types
  100. '((t :inherit (font-lock-type-face)))
  101. "Face for types."
  102. :group 'masm-mode-faces)
  103. (defface masm-instructions
  104. '((t :inherit (font-lock-builtin-face)))
  105. "Face for instructions."
  106. :group 'masm-mode-faces)
  107. (defface masm-directives
  108. '((t :inherit (font-lock-keyword-face)))
  109. "Face for directives."
  110. :group 'masm-mode-faces)
  111. (defface masm-labels
  112. '((t :inherit (font-lock-function-name-face)))
  113. "Face for labels."
  114. :group 'masm-mode-faces)
  115. (defface masm-subprogram
  116. '((t :inherit (font-lock-function-name-face)))
  117. "Face for subprogram."
  118. :group 'masm-mode-faces)
  119. (defface masm-macro
  120. '((t :inherit (font-lock-function-name-face)))
  121. "Face for macro."
  122. :group 'masm-mode-faces)
  123. (defface masm-section-name
  124. '((t :inherit (font-lock-type-face)))
  125. "Face for section name."
  126. :group 'masm-mode-faces)
  127. (defface masm-constant
  128. '((t :inherit (font-lock-constant-face)))
  129. "Face for constant."
  130. :group 'masm-mode-faces)
  131. (defface masm-struct
  132. '((t :inherit (font-lock-type-face)))
  133. "Face for struct."
  134. :group 'masm-mode-faces)
  135. (defface masm-union
  136. '((t :inherit (font-lock-type-face)))
  137. "Face for union."
  138. :group 'masm-mode-faces)
  139. (eval-and-compile
  140. (defconst masm-registers-common
  141. '("ah" "al" "ax" "bh" "bl" "bp" "bx" "ch" "cl" "cr0" "cr2" "cr3"
  142. "cs" "cx" "dh" "di" "dl" "dr0" "dr1" "dr2" "dr3" "dr6" "dr7"
  143. "ds" "dx" "eax" "ebp" "ebx" "ecx" "edi" "edx" "eip" "es" "esi"
  144. "esp" "fpr0" "fpr1" "fpr2" "fpr3" "fpr4" "fpr5" "fpr6" "fpr7"
  145. "fs" "gs" "ip" "mmx0" "mmx1" "mmx2" "mmx3" "mmx4" "mmx5" "mmx6"
  146. "mmx7" "si" "sp" "ss" "st" "tr3" "tr4" "tr5" "tr6" "tr7" )
  147. "MASM registers for `masm-mode'."))
  148. (eval-and-compile
  149. (defconst masm-registers-win64-only
  150. '( "r10" "r10b" "r10d" "r10w" "r11" "r11b" "r11d" "r11w" "r12"
  151. "r12b" "r12d" "r12w" "r13" "r13b" "r13d" "r13w" "r14" "r14b"
  152. "r14d" "r14w" "r15" "r15b" "r15d" "r15w" "r8" "r8b" "r8d" "r8w"
  153. "r9" "r9b" "r9d" "r9w" "rax" "rbp" "rbx" "rcx" "rdi" "rdx" "rip"
  154. "rsi" "rsp" "xmm0" "xmm1" "xmm10" "xmm11" "xmm12" "xmm13"
  155. "xmm14" "xmm15" "xmm2" "xmm3" "xmm4" "xmm5" "xmm6" "xmm7" "xmm8"
  156. "xmm9")
  157. "MASM win64 registers for `masm-mode'."))
  158. (eval-and-compile
  159. (defconst masm-instructions-common
  160. '("aaa" "aad" "aam" "aas" "adc" "adcx" "add" "addpd" "addps"
  161. "addsd" "addss" "addsubpd" "addsubps" "adox" "aesdec"
  162. "aesdeclast" "aesenc" "aesenclast" "aesimc" "aeskeygenassist"
  163. "and" "andn" "andnpd" "andnps" "andpd" "andps" "arpl" "bound"
  164. "bsf" "bsr" "bswap" "bt" "btc" "btr" "bts" "call" "clc" "cld"
  165. "cli" "clts" "cmp" "cmps" "cmpsb" "cmpsw" "cmpxchg" "cwd" "daa"
  166. "das" "dec" "div" "enter" "esc" "f2xm1" "fabs" "fadd" "faddp"
  167. "fbld" "fbstp" "fchs" "fclex" "fcom" "fcomp" "fcompp" "fcos"
  168. "fdecstp" "fdisi" "fdiv" "fdivp" "fdivr" "fdivrp" "feni" "ffree"
  169. "fiadd" "ficom" "ficomp" "fidiv" "fidivr" "fild" "fimul"
  170. "fincstp" "finit" "fist" "fistp" "fisub" "fisubr" "fld" "fld1"
  171. "fldcw" "fldenv" "fldenvd" "fldenvw" "fldl2e" "fldl2t" "fldlg2"
  172. "fldln2" "fldpi" "fldz" "fmul" "fmulp" "fnclex" "fndisi" "fneni"
  173. "fninit" "fnop" "fnsave" "fnsaved" "fnsavew" "fnstcw" "fnstenv"
  174. "fnstenvd" "fnstenvw" "fnstsw" "fpatan" "fprem" "fprem1" "fptan"
  175. "frndint" "frstor" "frstord" "frstorw" "fsave" "fsaved" "fsavew"
  176. "fscale" "fsetpm" "fsin" "fincos" "fsqrt" "fst" "fstcw" "fstenv"
  177. "fstenvd" "fstenvw" "fstp" "fstsw" "fsub" "fsubp" "fsubr"
  178. "fsubrp" "ftst" "fucom" "fucomp" "fucompp" "fwait" "fxam" "fxch"
  179. "fxtract" "fyl2x" "fyl2xp1" "hlt" "idiv" "imul" "in" "inc" "ins"
  180. "insb" "insd" "insw" "int" "into" "invd" "invlpg" "iret" "iretd"
  181. "iretdf" "iretf" "ja" "jae" "jb" "jbe" "jc" "jcxz" "je" "jecxz"
  182. "jg" "jge" "jl" "jle" "jmp" "jna" "jnae" "jnb" "jnbe" "jnc"
  183. "jne" "jng" "jnge" "jnl" "jnle" "jno" "jnp" "jns" "jnz" "jo"
  184. "jp" "jpe" "jpo" "js" "jz" "lahf" "lar" "lds" "lea" "leave"
  185. "les" "lfs" "lgdt" "lgs" "lidt" "lldt" "lmsw" "lods" "lodsb"
  186. "lodsd" "lodsw" "loop" "loopd" "loope" "looped" "loopew"
  187. "loopne" "loopned" "loopnz" "loopnzd" "loopnzw" "loopw" "loopz"
  188. "loopzd" "loopzw" "lsl" "lss" "ltr" "mov" "movapd" "movaps"
  189. "movbe" "movd" "movddup" "movdq2q" "movdqa" "movdqu" "movhlps"
  190. "movhpd" "movhps" "movlhps" "movlpd" "movlps" "movmskpd"
  191. "movmskps" "movntdq" "movntdqa" "movnti" "movntpd" "movntps"
  192. "movntq" "movntsd" "movntss" "movq" "movq2dq" "movs" "movsb"
  193. "movsd" "movsx" "movsw" "movzx" "mul" "nop" "not" "or" "out"
  194. "outs" "outsb" "outsd" "outsw" "pabsb" "pabsd" "pabsw"
  195. "packssdw" "packsswb" "packusdw" "packuswb" "paddb" "paddd"
  196. "paddq" "paddsb" "paddsiw" "paddsw" "paddusb" "paddusw" "paddw"
  197. "palignr" "pand" "pandn" "pause" "paveb" "pavgb" "pavgusb"
  198. "pavgw" "pblendvb" "pblendw" "pclmulhqhqdq" "pclmulhqlqdq"
  199. "pclmullqhqdq" "pclmullqlqdq" "pclmulqdq" "pcmpeqb" "pcmpeqd"
  200. "pcmpeqq" "pcmpeqw" "pcmpestri" "pcmpestrm" "pcmpgtb" "pcmpgtd"
  201. "pcmpgtq" "pcmpgtw" "pcmpistri" "pcmpistrm" "pdep" "pdistib"
  202. "pext" "pextrb" "pextrd" "pextrq" "pextrw" "pf2id" "pf2iw"
  203. "pfacc" "pfadd" "pfcmpeq" "pfcmpge" "pfcmpgt" "pfmax" "pfmin"
  204. "pfmul" "pfnacc" "pfpnacc" "pfrcp" "pfrcpit1" "pfrcpit2"
  205. "pfrcpv" "pfrsqit1" "pfrsqrt" "pfrsqrtv" "pfsub" "pfsubr"
  206. "phaddd" "phaddsw" "phaddw" "phminposuw" "phsubd" "phsubsw"
  207. "phsubw" "pi2fd" "pi2fw" "pinsrb" "pinsrd" "pinsrq" "pinsrw"
  208. "pmachriw" "pmaddubsw" "pmaddwd" "pmagw" "pmaxsb" "pmaxsd"
  209. "pmaxsw" "pmaxub" "pmaxud" "pmaxuw" "pminsb" "pminsd" "pminsw"
  210. "pminub" "pminud" "pminuw" "pmovmskb" "pmovsxbd" "pmovsxbq"
  211. "pmovsxbw" "pmovsxdq" "pmovsxwd" "pmovsxwq" "pmovzxbd"
  212. "pmovzxbq" "pmovzxbw" "pmovzxdq" "pmovzxwd" "pmovzxwq" "pmuldq"
  213. "pmulhriw" "pmulhrsw" "pmulhrwa" "pmulhrwc" "pmulhuw" "pmulhw"
  214. "pmulld" "pmullw" "pmuludq" "pmvgezb" "pmvlzb" "pmvnzb" "pmvzb"
  215. "pop" "popa" "popf" "popfd" "push" "pusha" "pushd" "pushf"
  216. "pushfd" "pushw" "rcl" "rcr" "ret" "retf" "retn" "rol" "ror"
  217. "sahf" "sal" "sar" "sbb" "scas" "scasb" "scasd" "scasw" "seta"
  218. "setae" "setb" "setbe" "setc" "sete" "setg" "setge" "setl"
  219. "setle" "setna" "setnae" "setnb" "setnbe" "setnc" "setne"
  220. "setng" "setnge" "setnl" "setnle" "setno" "setnp" "setns"
  221. "setnz" "seto" "setp" "setpe" "setpo" "sets" "setz" "shld" "shl"
  222. "shld" "shr" "shrd" "sidt" "sldt" "smsw" "stc" "std" "sti" "str"
  223. "stos" "stosb" "stosd" "stosw" "sub" "test" "verr" "verw"
  224. "wbinvd" "xadd" "xchg" "xlat" "xlatb" "xor")
  225. "MASM instructions for `masm-mode'."))
  226. (eval-and-compile
  227. (defconst masm-instructions-win32-only
  228. '("pushad" "popad")
  229. "MASM Win32 instructions for `masm-mode'."))
  230. (eval-and-compile
  231. (defconst masm-section-name
  232. '(".code" ".const" ".data" ".data?" ".stack")
  233. "MASM section names for `masm-mode'."))
  234. (eval-and-compile
  235. (defconst masm-types
  236. '("byte" "dword" "fword" "qword" "Real4" "Real8" "Real10" "sbyte"
  237. "sdword" "sword" "tbyte" "word")
  238. "MASM types for `masm-mode'."))
  239. (eval-and-compile
  240. (defconst masm-prefix
  241. '("lock" "rep" "repe" "repne" "repnz" "repz")
  242. "MASM prefixes for `masm-mode'."))
  243. (eval-and-compile
  244. (defconst masm-directives-win32-only
  245. '(".186" ".286" ".286c" ".286p" ".287" ".386" ".386c" ".386p"
  246. ".387" ".486" ".486p" ".8086" ".8087" ".alpha" ".break"
  247. ".continue" ".cref" ".dosseg" ".else" ".elseif" ".endif" ".endw"
  248. ".err" ".err1" ".err2" ".errb" ".errdef" ".errdif" ".errdifi"
  249. ".erre" ".erridn" ".erridni" ".errnb" ".errndef" ".errnz"
  250. ".exit" ".fardata" ".fardata?" ".if" ".lall" ".lfcond" ".list"
  251. ".listall" ".listif" ".listmacro" ".listmacroall" ".mmx"
  252. ".model" ".msfloat" ".no87" ".nocref" ".nolist" ".nolistif"
  253. ".nolistmacro" ".radix"".repeat" ".sall" ".seq" ".sfcond"
  254. ".startup" ".tfcond" ".type" ".until" ".untilcxz" ".while"
  255. ".xall" ".xcref" ".xlist" "%out" "carry?" "invoke" "overflow?"
  256. "parity?" "sign?" "zero?")
  257. "MASM win32 directives for `masm-mode'."))
  258. (eval-and-compile
  259. (defconst masm-directives-common
  260. '("alias" "align" "assume" "catstr" "comm" "comment" "db" "dd"
  261. "df" "dosseg" "dq" "dt" "dup" "dw" "echo" "else" "elseif"
  262. "elseif1" "elseif2" "elseifb" "elseifdef" "elseifdif"
  263. "elseifdifi" "elseife" "elseifidn" "elseifidni" "elseifnb"
  264. "elseifndef" "end" "endif" "endm" "endp" "ends" "eq" "equ"
  265. "even" "exitm" "extern" "externdef" "extrn" "for" "forc""ge"
  266. "goto" "group" "gt" "high" "highword" "if" "if1" "if2" "ifb"
  267. "ifdef" "ifdif" "ifdifi" "ife" "ifidn" "ifidni" "ifnb" "ifndef"
  268. "include" "includelib" "instr" "irp" "irpc" "label" "le"
  269. "length" "lengthof" "local" "low" "lowword" "lroffset" "lt"
  270. "macro" "mask" "mod" "name" "ne" "offset" "opattr" "option"
  271. "org" "page" "popcontext" "proc" "proto" "ptr" "public" "purge"
  272. "pushcontext" "record" "repeat" "rept" "seg" "segment" "short"
  273. "size" "sizeof" "sizestr" "struc" "struct" "substr" "subtitle"
  274. "subttl" "textequ" "this" "title" "type" "typedef" "union" "uses"
  275. "while")
  276. "MASM directives for `masm-mode'."))
  277. (defconst masm-label-regexp
  278. "\\(\\_<[a-zA-Z_@][a-zA-Z0-9_@?]*\\_>\\):\\s-*"
  279. "Regexp for `masm-mode' for matching labels.")
  280. (defconst masm-subprogram-regexp
  281. "\\(\\_<[a-zA-Z_@]+\\_>\\)[ \t]+\\(proc\\|endp\\)\\s-*"
  282. "Regexp for `masm-mode' for matching subprogram.")
  283. (defconst masm-constant-regexp
  284. "\\<[-+]?\\([0-9]+[Dd]?\\|[01]+[Bb]\\|[0-7]+[Qq]\\|[0-9A-Fa-f]+[Hh]\\)\\([-+]\\([0-9]+[Dd]?\\|[01]+[Bb]\\|[0-7]+[Qq]\\|[0-9A-Fa-f]+[Hh]\\)\\)*\\>"
  285. "Regexp for `masm-mode' for matching numeric constants.")
  286. (defconst masm-struct-regexp
  287. "\\(\\_<[a-zA-Z_@]+\\_>\\)[ \t]+\\(struct\\|ends\\)\\s-*"
  288. "Regexp for `masm-mode' for matching struct.")
  289. (defconst masm-union-regexp
  290. "\\(\\_<[a-zA-Z_@]+\\_>\\)[ \t]+\\(union\\|ends\\)\\s-*"
  291. "Regexp for `masm-mode' for matching struct.")
  292. (defconst masm-macro-regexp
  293. "\\(\\_<[a-zA-Z_@]+\\_>\\)[ \t]+macro\\s-*"
  294. "Regexp for `masm-mode' for matching macro.")
  295. (defmacro masm--opt (keywords)
  296. "Prepare KEYWORDS for `looking-at'."
  297. `(eval-when-compile
  298. (regexp-opt ,keywords 'words)))
  299. (defconst masm-imenu-generic-expression
  300. `((nil ,(concat "^\\s-*" masm-label-regexp) 1)
  301. (nil ,(concat "\\(\\_<[a-zA-Z_@]+\\_>\\)[ \t]+"
  302. (masm--opt '("proc" "macro")))
  303. 1))
  304. "Expressions for `imenu-generic-expression'.")
  305. (defconst masm-win32-font-lock-keywords
  306. `((,(masm--opt masm-section-name) . 'masm-section-name)
  307. (,(masm--opt masm-registers-common) . 'masm-registers)
  308. (,(masm--opt masm-types) . 'masm-types)
  309. (,(masm--opt masm-instructions-common) . 'masm-instructions)
  310. (,(masm--opt masm-instructions-win32-only) . 'masm-instructions)
  311. (,(masm--opt masm-prefix) . 'masm-prefix)
  312. (,masm-label-regexp (1 'masm-labels))
  313. (,masm-subprogram-regexp (1 'masm-subprogram))
  314. (,masm-constant-regexp . 'masm-constant)
  315. (,masm-struct-regexp (1 'masm-struct))
  316. (,masm-union-regexp (1 'masm-union))
  317. (,masm-macro-regexp (1 'masm-macro))
  318. (,(masm--opt masm-directives-common) . 'masm-directives)
  319. (,(masm--opt masm-directives-win32-only) . 'masm-directives))
  320. "Win32 keywords for `masm-mode'.")
  321. (defconst masm-win64-font-lock-keywords
  322. `((,(masm--opt masm-section-name) . 'masm-section-name)
  323. (,(masm--opt masm-registers-common) . 'masm-registers)
  324. (,(masm--opt masm-registers-win64-only) . 'masm-registers)
  325. (,(masm--opt masm-types) . 'masm-types)
  326. (,(masm--opt masm-instructions-common) . 'masm-instructions)
  327. (,(masm--opt masm-prefix) . 'masm-prefix)
  328. (,masm-label-regexp (1 'masm-labels))
  329. (,masm-subprogram-regexp (1 'masm-subprogram))
  330. (,masm-constant-regexp . 'masm-constant)
  331. (,masm-struct-regexp (1 'masm-struct))
  332. (,masm-union-regexp (1 'masm-union))
  333. (,masm-macro-regexp (1 'masm-macro))
  334. (,(masm--opt masm-directives-common) . 'masm-directives))
  335. "Win64 keywords for `masm-mode'.")
  336. (defconst masm-mode-syntax-table
  337. (with-syntax-table (copy-syntax-table)
  338. (modify-syntax-entry ?_ "w")
  339. (modify-syntax-entry ?@ "w")
  340. (modify-syntax-entry ?\? "w")
  341. (modify-syntax-entry ?\. "w")
  342. (modify-syntax-entry ?\; "<")
  343. (modify-syntax-entry ?\n ">")
  344. (modify-syntax-entry ?\" "\"")
  345. (modify-syntax-entry ?\' "\"")
  346. (syntax-table))
  347. "Syntax table for `masm-mode'.")
  348. (defvar masm-mode-map
  349. (let ((map (make-sparse-keymap)))
  350. ;; Note that the comment character isn't set up until masm-mode is called.
  351. (define-key map ":" #'masm-colon)
  352. (define-key map "\C-c;" #'comment-region)
  353. (define-key map ";" #'masm-comment)
  354. (define-key map "\C-j" #'masm-newline-and-indent)
  355. (define-key map "\C-m" #'masm-newline-and-indent)
  356. (define-key map "\C-c\C-c" #'masm-build)
  357. (define-key map "\C-c\C-b" #'masm-compile)
  358. (define-key map "\C-c\C-l" #'masm-link)
  359. (define-key map "\C-c\C-s" #'masm-change-program-type)
  360. (define-key map [menu-bar masm-mode] (cons "Masm" (make-sparse-keymap)))
  361. (define-key map [menu-bar masm-mode newline-and-indent]
  362. '(menu-item "Insert Newline and Indent" masm-newline-and-indent
  363. :help "Insert a newline, then indent according to major mode"))
  364. (define-key map [menu-bar masm-mode masm-colon]
  365. '(menu-item "Insert Colon" masm-colon
  366. :help "Insert a colon; if it follows a label, delete the label's indentation"))
  367. (define-key map [menu-bar masm-mode masm-change-program-type]
  368. '(menu-item "Switch program type" masm-change-program-type
  369. :help "Switch between Win32 and Win64"))
  370. (define-key map [menu-bar masm-mode masm-link]
  371. '(menu-item "Link the obj file" masm-link
  372. :help "Use link to link the obj file"))
  373. (define-key map [menu-bar masm-mode masm-compile]
  374. '(menu-item "Compile the file" masm-compile
  375. :help "Use ml64 to compile the file"))
  376. (define-key map [menu-bar masm-mode masm-build]
  377. '(menu-item "Build the project" masm-build
  378. :help "Use nmake to build the project"))
  379. (define-key map [menu-bar masm-mode comment-region]
  380. '(menu-item "Comment Region" comment-region
  381. :help "Comment or uncomment each line in the region"))
  382. map)
  383. "Keymap for masm mode.")
  384. (defun masm-colon ()
  385. "Insert a colon and convert the current line into a label."
  386. (interactive)
  387. (call-interactively #'self-insert-command)
  388. (save-excursion
  389. (back-to-indentation)
  390. (delete-horizontal-space)))
  391. (defun masm-newline-and-indent ()
  392. "Auto-indent the new line."
  393. (interactive)
  394. (let ((indent
  395. (save-excursion
  396. (back-to-indentation)
  397. (current-column)))
  398. (col (current-column)))
  399. (newline-and-indent)
  400. (if (eql indent col)
  401. (indent-line-to indent))))
  402. (defun masm--current-line ()
  403. "Return the current line as a string."
  404. (save-excursion
  405. (let ((start (line-beginning-position))
  406. (end (line-end-position)))
  407. (buffer-substring-no-properties start end))))
  408. (defun masm--empty-line-p ()
  409. "Return non-nil if current line has non-whitespace."
  410. (not (string-match-p "\\S-" (masm--current-line))))
  411. (defun masm--line-has-comment-p ()
  412. "Return non-nil if current line contain a comment."
  413. (save-excursion
  414. (end-of-line)
  415. (nth 4 (syntax-ppss))))
  416. (defun masm--line-has-non-comment-p ()
  417. "Return non-nil of the current line has code."
  418. (let* ((line (masm--current-line))
  419. (match (string-match-p "\\S-" line)))
  420. (when match
  421. (not (eql ?\; (aref line match))))))
  422. (defun masm--inside-indentation-p ()
  423. "Return non-nil if point is within the indentation."
  424. (save-excursion
  425. (let ((point (point))
  426. (start (line-beginning-position))
  427. (end (save-excursion (back-to-indentation) (point))))
  428. (and (<= start point) (<= point end)))))
  429. (defun masm-insert-comment ()
  430. "Insert a comment if the current line doesn’t contain one."
  431. (let ((comment-insert-comment-function nil))
  432. (if (or (masm--empty-line-p) (nth 3 (syntax-ppss)))
  433. (progn
  434. (indent-line-to 0)
  435. (insert ";"))
  436. (comment-indent))))
  437. (defun masm-comment (&optional arg)
  438. "Begin or edit a comment with context-sensitive placement.
  439. The right-hand comment gutter is far away from the code, so this
  440. command uses the mark ring to help move back and forth between
  441. code and the comment gutter.
  442. * If no comment gutter exists yet, mark the current position and
  443. jump to it.
  444. * If already within the gutter, pop the top mark and return to
  445. the code.
  446. * If on a line with no code, just insert a comment character.
  447. * If within the indentation, just insert a comment character.
  448. This is intended prevent interference when the intention is to
  449. comment out the line.
  450. With a prefix ARG, kill the comment on the current line with
  451. `comment-kill'."
  452. (interactive "p")
  453. (if (not (eql arg 1))
  454. (comment-kill nil)
  455. (cond
  456. ;; Empty line, or inside a string? Insert.
  457. ((or (masm--empty-line-p) (nth 3 (syntax-ppss)))
  458. (indent-line-to 0)
  459. (insert ";"))
  460. ;; Inside the indentation? Comment out the line.
  461. ((masm--inside-indentation-p)
  462. (insert ";"))
  463. ;; Currently in a right-side comment? Return.
  464. ((and (masm--line-has-comment-p)
  465. (masm--line-has-non-comment-p)
  466. (nth 4 (syntax-ppss)))
  467. (setf (point) (mark))
  468. (pop-mark))
  469. ;; Line has code? Mark and jump to right-side comment.
  470. ((masm--line-has-non-comment-p)
  471. (push-mark)
  472. (comment-indent))
  473. ;; Otherwise insert.
  474. ((insert ";")))))
  475. (defun masm-compile (_savep command)
  476. "Compile COMMAND in `masm-mode'."
  477. (interactive
  478. (list (if (buffer-modified-p)
  479. (let ((savep (y-or-n-p (format "Buffer %s modified; Save it before compile? " (current-buffer)))))
  480. (if savep
  481. (save-buffer))))
  482. (if masm--compile-command-used
  483. (read-shell-command "Compile command: " masm--compile-command-used)
  484. (let ((command (if masm--program-win64
  485. (concat
  486. "ml64 "
  487. (mapconcat (lambda (str) str) masm-win64-compile-args " ")
  488. " "
  489. (file-name-nondirectory buffer-file-name))
  490. (concat
  491. "ml "
  492. (mapconcat (lambda (str) str) masm-win32-compile-args " ")
  493. " "
  494. (file-name-nondirectory buffer-file-name)))))
  495. (read-shell-command "Compile command: " command)))))
  496. (setq masm--compile-command-used command)
  497. (if masm--program-win64
  498. (let ((process-environment
  499. (append
  500. (list
  501. (concat "PATH=" masm-win64-executable-path ";"
  502. (getenv "path"))
  503. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win64-include-path ";")
  504. (getenv "include"))
  505. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win64-library-path ";")
  506. (getenv "lib")))
  507. process-environment)))
  508. (compilation-start
  509. command nil (lambda (_maj-mode)
  510. "*masm x64 compile*")))
  511. (let ((process-environment
  512. (append
  513. (list
  514. (concat "PATH=" masm-win32-executable-path ";"
  515. (getenv "path"))
  516. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win32-include-path ";")
  517. (getenv "include"))
  518. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win32-library-path ";")
  519. (getenv "lib")))
  520. process-environment)))
  521. (compilation-start
  522. command nil (lambda (_maj-mode)
  523. "*masm x86 compile*")))))
  524. (defun masm-link (command)
  525. "Compile COMMAND in `masm-mode'."
  526. (interactive
  527. (list (if masm--link-command-used
  528. (read-shell-command "Link command: " masm--link-command-used)
  529. (let ((command (concat
  530. "link "
  531. (mapconcat (lambda (str) str) (if masm--program-win64 masm-win64-link-args masm-win32-link-args) " ")
  532. " "
  533. (file-name-base buffer-file-name)
  534. ".obj")))
  535. (read-shell-command "Link command: " command)))))
  536. (setq masm--link-command-used command)
  537. (if masm--program-win64
  538. (let ((process-environment
  539. (append
  540. (list
  541. (concat "PATH=" masm-win64-executable-path ";"
  542. (getenv "path"))
  543. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win64-include-path ";")
  544. (getenv "include"))
  545. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win64-library-path ";")
  546. (getenv "lib")))
  547. process-environment)))
  548. (compilation-start
  549. command nil (lambda (_maj-mode)
  550. "*masm x64 link*")))
  551. (let ((process-environment
  552. (append
  553. (list
  554. (concat "PATH=" masm-win32-executable-path ";"
  555. (getenv "path"))
  556. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win32-include-path ";")
  557. (getenv "include"))
  558. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win32-library-path ";")
  559. (getenv "lib")))
  560. process-environment)))
  561. (compilation-start
  562. command nil (lambda (_maj-mode)
  563. "*masm x86 link*")))))
  564. (defun masm-build (_savep command)
  565. "Build COMMAND in `masm-mode'."
  566. (interactive
  567. (list (if (buffer-modified-p)
  568. (let ((savep (y-or-n-p (format "Buffer %s modified; Save it before build? " (current-buffer)))))
  569. (if savep
  570. (save-buffer))))
  571. (if masm--build-command-used
  572. (read-shell-command "Build command: " masm--build-command-used)
  573. (let ((command (concat
  574. masm-build-executable " "
  575. (mapconcat (lambda (str) str) masm-build-args " "))))
  576. (read-shell-command "Build command: " command)))))
  577. (setq masm--build-command-used command)
  578. (if masm--program-win64
  579. (let ((process-environment
  580. (append
  581. (list
  582. (concat "PATH=" masm-win64-executable-path ";"
  583. (getenv "path"))
  584. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win64-include-path ";")
  585. (getenv "include"))
  586. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win64-library-path ";")
  587. (getenv "lib")))
  588. process-environment)))
  589. (compilation-start
  590. command nil (lambda (_maj-mode)
  591. "*masm x64 build*")))
  592. (let ((process-environment
  593. (append
  594. (list
  595. (concat "PATH=" masm-win32-executable-path ";"
  596. (getenv "path"))
  597. (concat "INCLUDE=" (mapconcat #'file-name-as-directory masm-win32-include-path ";")
  598. (getenv "include"))
  599. (concat "LIB=" (mapconcat #'file-name-as-directory masm-win32-library-path ";")
  600. (getenv "lib")))
  601. process-environment)))
  602. (compilation-start
  603. command nil (lambda (_maj-mode)
  604. "*masm x86 build*")))))
  605. (defun masm-win32 ()
  606. "Change to Win32 highlighting."
  607. (interactive)
  608. (setq-local masm--program-win64 nil)
  609. (setq-local font-lock-keywords masm-win32-font-lock-keywords)
  610. (font-lock-flush))
  611. (defun masm-win64 ()
  612. "Change to Win64 highlighting."
  613. (interactive)
  614. (setq-local masm--program-win64 t)
  615. (setq-local font-lock-keywords masm-win64-font-lock-keywords)
  616. (font-lock-flush))
  617. (defun masm-change-program-type ()
  618. "Switch program highlighting."
  619. (interactive)
  620. (if masm--program-win64
  621. (call-interactively #'masm-win32)
  622. (call-interactively #'masm-win64)))
  623. (defun masm-mode-before ()
  624. "Delay this to a hook instead of running it immediately, due the order in which file local variables are processed."
  625. (unless (eql masm--program-win64 masm-program-win64)
  626. (setq masm--program-win64 masm-program-win64)
  627. (if masm-program-win64
  628. (setq-local font-lock-keywords masm-win64-font-lock-keywords)
  629. (setq-local font-lock-keywords masm-win32-font-lock-keywords))))
  630. ;;;###autoload
  631. (define-derived-mode masm-mode prog-mode "MASM"
  632. "Major mode for editing MASM assembly programs."
  633. :group 'masm-mode
  634. (setq local-abbrev-table masm-mode-abbrev-table)
  635. (if masm--program-win64
  636. (setq-local font-lock-defaults '(masm-win64-font-lock-keywords nil :case-fold))
  637. (setq-local font-lock-defaults '(masm-win32-font-lock-keywords nil :case-fold)))
  638. (setq-local comment-start ";")
  639. (setq-local comment-insert-comment-function #'masm-insert-comment)
  640. (setq-local imenu-generic-expression masm-imenu-generic-expression)
  641. (add-hook 'after-change-major-mode-hook #'masm-mode-before t t))
  642. ;;;###autoload
  643. (add-to-list 'auto-mode-alist '("\\.asm\\'" . masm-mode))
  644. ;;;###autoload
  645. (add-to-list 'auto-mode-alist '("\\.inc\\'" . masm-mode))
  646. (provide 'masm-mode)
  647. ;; Local Variables:
  648. ;; indent-tabs-mode: nil
  649. ;; End:
  650. ;;; masm-mode.el ends here