/ solidity-mode.el
solidity-mode.el
  1  ;;; solidity-mode.el --- Major mode for ethereum's solidity language
  2  
  3  ;; Copyright (C) 2015-2020  Lefteris Karapetsas
  4  
  5  ;; Author: Lefteris Karapetsas  <lefteris@refu.co>
  6  ;; Keywords: languages, solidity
  7  ;; Version: 0.1.11
  8  
  9  ;; This program is free software; you can redistribute it and/or modify
 10  ;; it under the terms of the GNU General Public License as published by
 11  ;; the Free Software Foundation, either version 3 of the License, or
 12  ;; (at your option) any later version.
 13  
 14  ;; This program is distributed in the hope that it will be useful,
 15  ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
 16  ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17  ;; GNU General Public License for more details.
 18  
 19  ;; You should have received a copy of the GNU General Public License
 20  ;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
 21  
 22  ;;; Commentary:
 23  
 24  ;; Solidity is a high-level language whose syntax is similar to that
 25  ;; of Javascript and it is designed to compile to code for the
 26  ;; Ethereum Virtual Machine.
 27  
 28  ;; This package provides a major mode for writing Solidity code.
 29  
 30  ;;; Code:
 31  
 32  (require 'cc-mode)
 33  (require 'solidity-common)
 34  
 35  
 36  ;;; --- Customizable variables ---
 37  (defgroup solidity nil
 38    "Major mode for ethereum's solidity language"
 39    :group 'languages ;; Emacs -> Programming -> Languages
 40    :prefix "solidity-"
 41    :link '(url-link :tag "Github" "https://github.com/ethereum/emacs-solidity"))
 42  
 43  (defcustom solidity-mode-hook nil
 44    "Callback hook to execute whenever a solidity file is loaded."
 45    :type 'hook
 46    :group 'solidity)
 47  
 48  (defcustom solidity-comment-style 'star
 49    "Denotes the style of comments to use for solidity when commenting.
 50  
 51  This option will define what kind of comments will be input into the buffer by
 52  commands like `comment-region'.  The default value is 'star.
 53  Possible values are:
 54  
 55  `star'
 56      Follow the same styling as C mode does by default having all comments
 57      obey the /* .. */ style.
 58  
 59  `slash'
 60      All comments will start with //."
 61    :group 'solidity
 62    :type '(choice (const :tag "Commenting starts with /* and ends with */" star)
 63                   (const :tag "Commenting starts with //" slash))
 64    :package-version '(solidity . "0.1.7")
 65    :safe #'symbolp)
 66  
 67  (defcustom solidity-mode-disable-c-mode-hook t
 68    "If non-nil, do not run `c-mode-hook'."
 69    :group 'solidity
 70    :type 'boolean)
 71  
 72  (defvar solidity-mode-map
 73    (let ((map (make-sparse-keymap)))
 74      (define-key map "\C-j" 'newline-and-indent)
 75      (define-key map (kbd "C-c C-g") 'solidity-estimate-gas-at-point)
 76      map)
 77    "Keymap for solidity major mode.")
 78  
 79  (defvar solidity-checker t "The solidity flycheck syntax checker.")
 80  (defvar solidity-mode t "The solidity major mode.")
 81  
 82  ;;;###autoload
 83  (add-to-list 'auto-mode-alist '("\\.sol\\'" . solidity-mode))
 84  
 85  (defun solidity-filter (condp lst)
 86    "A simple version of a list filter.  Depending on CONDP filter LST."
 87    (delq nil
 88          (mapcar (lambda (x) (and (funcall condp x) x)) lst)))
 89  
 90  (defconst solidity-keywords
 91    '("abstract"
 92      "after"
 93      "anonymous"
 94      "as"
 95      "assembly"
 96      "assert"
 97      "break"
 98      "calldata"
 99      "catch"
100      "constant"
101      "constructor"
102      "continue"
103      "contract"
104      "default"
105      "delete"
106      "do"
107      "else"
108      "emit"
109      "enum"
110      "event"
111      "error"
112      "external"
113      "fallback"
114      "for"
115      "function"
116      "if"
117      "immutable"
118      "import"
119      "in"
120      "indexed"
121      "interface"
122      "internal"
123      "is"
124      "library"
125      "mapping"
126      "memory"
127      "modifier"
128      "new"
129      "override"
130      "payable"
131      "pragma"
132      "private"
133      "public"
134      "pure"
135      "receive"
136      "require"
137      "return"
138      "returns"
139      "revert"
140      "storage"
141      "struct"
142      "switch"
143      "this"
144      "throw"
145      "try"
146      "unchecked"
147      "using"
148      "var"
149      "view"
150      "virtual"
151      "while"
152      )
153    "Keywords of the solidity language.")
154  
155  (defconst solidity-tofontify-keywords
156    (solidity-filter
157     (lambda (x) (not (member x '("contract"
158                                  "library"))))
159     solidity-keywords)
160    "Keywords that will be fontified as they are and don't have special rules."
161    )
162  
163  (defconst solidity-constants
164    '("true" "false"
165      "wei"
166      "szabo"
167      "finney"
168      "ether"
169      "seconds"
170      "minutes"
171      "hours"
172      "days"
173      "weeks"
174      "years"
175      )
176    "Constants in the solidity language.")
177  
178  (defconst solidity-variable-modifier
179    '("constant"
180      "public"
181      "immutable"
182      "indexed"
183      "storage"
184      "memory"
185      "calldata"
186      )
187    "Modifiers of variables in solidity.")
188  
189  (defconst solidity-builtin-types
190    '("address"
191      "bool"
192      "bytes"
193      "bytes0"
194      "bytes1"
195      "bytes2"
196      "bytes3"
197      "bytes4"
198      "bytes5"
199      "bytes6"
200      "bytes7"
201      "bytes8"
202      "bytes9"
203      "bytes10"
204      "bytes11"
205      "bytes12"
206      "bytes13"
207      "bytes14"
208      "bytes15"
209      "bytes16"
210      "bytes17"
211      "bytes18"
212      "bytes19"
213      "bytes20"
214      "bytes21"
215      "bytes22"
216      "bytes23"
217      "bytes24"
218      "bytes25"
219      "bytes26"
220      "bytes27"
221      "bytes28"
222      "bytes29"
223      "bytes30"
224      "bytes31"
225      "bytes32"
226      "int"
227      "int8"
228      "int16"
229      "int24"
230      "int32"
231      "int40"
232      "int48"
233      "int56"
234      "int64"
235      "int72"
236      "int80"
237      "int88"
238      "int96"
239      "int104"
240      "int112"
241      "int120"
242      "int128"
243      "int136"
244      "int144"
245      "int152"
246      "int160"
247      "int168"
248      "int176"
249      "int184"
250      "int192"
251      "int200"
252      "int208"
253      "int216"
254      "int224"
255      "int232"
256      "int240"
257      "int248"
258      "int256"
259  
260      "let"
261      "mapping"
262      "real"
263      "string"
264      "text"
265  
266      "uint"
267      "uint8"
268      "uint16"
269      "uint24"
270      "uint32"
271      "uint40"
272      "uint48"
273      "uint56"
274      "uint64"
275      "uint72"
276      "uint80"
277      "uint88"
278      "uint96"
279      "uint104"
280      "uint112"
281      "uint120"
282      "uint128"
283      "uint136"
284      "uint144"
285      "uint152"
286      "uint160"
287      "uint168"
288      "uint176"
289      "uint184"
290      "uint192"
291      "uint200"
292      "uint208"
293      "uint216"
294      "uint224"
295      "uint232"
296      "uint240"
297      "uint248"
298      "uint256"
299  
300      "ureal")
301    "Built in data types of the solidity language.")
302  
303  (defconst solidity-builtin-constructs
304    '("msg"
305      "block"
306      "tx")
307    "Built in constructs of the solidity language.")
308  
309  (defvar solidity-identifier-regexp
310    "\\([a-zA-Z0-9]\\|_\\)+")
311  
312  (defvar solidity-variable-attributes
313    "\\(&\\|*\\|~\\)"
314    "Variable attributes like references '&' e.t.c.")
315  
316  ;; Set font lock options.
317  ;; For information on the various faces check here:
318  ;; http://www.gnu.org/software/emacs/manual/html_node/ccmode/Faces.html
319  ;; For examples on how to make advanced fontification based on the
320  ;; language rules check C mode here:
321  ;; http://cc-mode.sourceforge.net/src/cc-fonts.el
322  ;;
323  ;; Guide for Searh based fontification:
324  ;; http://ergoemacs.org/emacs_manual/elisp/Search_002dbased-Fontification.html
325  ;; General colouring guide:
326  ;; http://ergoemacs.org/emacs/elisp_syntax_coloring.html
327  (defconst solidity-font-lock-keywords
328    (list
329     `(,(regexp-opt solidity-tofontify-keywords 'words) . font-lock-keyword-face)
330     `(,(regexp-opt solidity-builtin-types 'words) . font-lock-type-face)
331     `(,(regexp-opt solidity-builtin-constructs 'words) . font-lock-builtin-face)
332     '(solidity-match-functions (1 font-lock-type-face)
333                                (2 font-lock-function-name-face))
334     '(solidity-match-mappings (1 font-lock-type-face)
335                               (3 font-lock-function-name-face))
336     '(solidity-match-pragma-stmt (1 font-lock-preprocessor-face)
337                                   (2 font-lock-string-face))
338     '(solidity-match-library-decl (1 font-lock-keyword-face)
339                                   (2 font-lock-variable-name-face))
340     '(solidity-match-contract-decl (1 font-lock-keyword-face)
341                                    (2 font-lock-variable-name-face))
342     '(solidity-match-interface-decl (1 font-lock-keyword-face)
343                                    (2 font-lock-variable-name-face))
344     '(solidity-match-modifier-decl (1 font-lock-keyword-face)
345                                  (2 font-lock-variable-name-face))
346     '(solidity-match-struct-decl (1 font-lock-keyword-face)
347                                  (2 font-lock-variable-name-face))
348     '(solidity-match-event-decl (1 font-lock-keyword-face)
349                                    (2 font-lock-variable-name-face))
350     '(solidity-match-error-decl (1 font-lock-keyword-face)
351                                 (2 font-lock-variable-name-face))
352     '(solidity-match-user-defined-value-type-decl (1 font-lock-keyword-face)
353                                 (2 font-lock-variable-name-face))
354     '(solidity-match-variable-decls (1 font-lock-keyword-face)
355                                     (2 font-lock-variable-name-face))
356     `(,(regexp-opt solidity-constants 'words) . font-lock-constant-face))
357    "The font lock options for solidity.")
358  
359  (defun solidity-match-regexp (re limit)
360    "Generic regular expression matching wrapper for RE with a given LIMIT."
361    (re-search-forward re
362                       limit ; search bound
363                       t     ; no error, return nil
364                       nil   ; do not repeat
365                       ))
366  
367  (defun solidity-match-contract-decl (limit)
368    "Search the buffer forward until LIMIT matching contract declarations.
369  
370  First match should be a keyword and second an identifier."
371    (solidity-match-regexp
372     (concat
373      " *\\(\\<contract\\>\\) +\\(" solidity-identifier-regexp "\\)")
374     limit))
375  
376  (defun solidity-match-interface-decl (limit)
377    "Search the buffer forward until LIMIT matching interface declarations.
378  
379  First match should be a keyword and second an identifier."
380    (solidity-match-regexp
381     (concat
382      " *\\(\\<interface\\>\\) +\\(" solidity-identifier-regexp "\\)")
383     limit))
384  
385  (defun solidity-match-library-decl (limit)
386    "Search the buffer forward until LIMIT matching library declarations.
387  
388  First match should be a keyword and second an identifier."
389    (solidity-match-regexp
390     (concat
391      " *\\(\\<library\\>\\) +\\(" solidity-identifier-regexp "\\)")
392     limit))
393  
394  (defun solidity-match-pragma-stmt (limit)
395    "Search the buffer forward until LIMIT matching pragma statements.
396  
397  First match should be a keyword and second an identifier."
398    (solidity-match-regexp
399     (concat
400      " *\\(\\<pragma\\>\\) +\\(.*\\);")
401     limit))
402  
403  (defun solidity-match-struct-decl (limit)
404    "Search the buffer forward until LIMIT matching struct declarations.
405  
406  First match should be a keyword and second an identifier."
407    (solidity-match-regexp
408     (concat
409      " *\\(\\<struct\\>\\) +\\(" solidity-identifier-regexp "\\)")
410     limit))
411  
412  (defun solidity-match-functions (limit)
413    "Search the buffer forward until LIMIT matching function names.
414  
415  Highlight the 1st result."
416    (solidity-match-regexp
417     (concat
418      " *\\(\\<function\\>\\) +\\(" solidity-identifier-regexp "\\)")
419     limit))
420  
421  (defun solidity-match-event-decl (limit)
422    "Search the buffer forward until LIMIT matching function names.
423  
424  Highlight the 1st result."
425    (solidity-match-regexp
426     (concat
427      " *\\(\\<event\\>\\) +\\(" solidity-identifier-regexp "\\)")
428     limit))
429  
430  (defun solidity-match-error-decl (limit)
431    "Search the buffer forward until LIMIT matching error names.
432  
433  Highlight the 1st result."
434    (solidity-match-regexp
435     (concat
436      " *\\(\\<error\\>\\) +\\(" solidity-identifier-regexp "\\)")
437     limit))
438  
439  (defun solidity-match-user-defined-value-type-decl (limit)
440    "Search the buffer forward until LIMIT matching user defined value type names.
441  
442  Highlight the 1st result."
443    (solidity-match-regexp
444     (concat
445      " *\\(\\<type\\>\\) +\\(" solidity-identifier-regexp "\\)")
446     limit))
447  
448  (defun solidity-match-modifier-decl (limit)
449    "Search the buffer forward until LIMIT matching function names.
450  
451  Highlight the 1st result."
452    (solidity-match-regexp
453     (concat
454      " *\\(\\<modifier\\>\\) +\\(" solidity-identifier-regexp "\\)")
455     limit))
456  
457  (defun solidity-match-mappings (limit)
458    "Search the buffer forward until LIMIT matching solidity mappings.
459  
460  Highlight the 1st result."
461    (solidity-match-regexp
462     (concat
463      " *\\(\\<mapping\\>\\) *(.*) *\\("(regexp-opt solidity-variable-modifier) " \\)*\\(" solidity-identifier-regexp "\\)")
464     limit))
465  
466  (defun solidity-match-variable-decls (limit)
467    "Search the buffer forward until LIMIT matching variable declarations.
468  
469  Highlight the 1st result."
470    (solidity-match-regexp
471     (concat
472      " *\\(" (regexp-opt solidity-builtin-types 'words) " *\\(?:\\[ *[0-9]*\\]\\)* *\\) " "\\(?:"(regexp-opt solidity-variable-modifier 'words) " \\)* *\\(" solidity-identifier-regexp "\\)")
473     limit))
474  
475  ;; solidity syntax table
476  (defvar solidity-mode-syntax-table
477    (let ((st (make-syntax-table)))
478      ;; Underscore ('_') and dollar sign ('$') are valid parts of a word.
479      (modify-syntax-entry ?_ "w" st)
480      (modify-syntax-entry ?$ "w" st)
481      ;; c++ style comments in the syntax table
482      ;; more info on the syntax flags here:
483      ;; http://www.gnu.org/software/emacs/manual/html_node/elisp/Syntax-Flags.html
484      (modify-syntax-entry ?/ ". 124b" st)
485      (modify-syntax-entry ?* ". 23" st)
486      (modify-syntax-entry ?\n "> b" st)
487      st)
488    "Syntax table for the solidity language.")
489  
490  
491  (defun solidity--re-matches (regexp string count)
492    "Get a list of all REGEXP match results in a STRING.
493  
494  COUNT is the parenthentical subexpression for which to return matches.
495  If your provide 0 for COUNT then the entire regex match is returned."
496    (save-match-data
497      (let ((pos 0)
498            matches)
499        (while (string-match regexp string pos)
500          (push (match-string-no-properties count string) matches)
501          (setq pos (match-end 0)))
502        matches)))
503  
504  (defun solidity--handle-gasestimate-finish (process event)
505    "Handle all events from the solc gas estimation PROCESS.
506  EVENT is isgnored."
507    (when (memq (process-status process) '(signal exit))
508      (let* ((buffer (process-buffer process))
509             (funcname (process-get process 'solidity-gasestimate-for-function))
510             (filename (process-get process 'solidity-gasestimate-for-filename))
511             (output (with-current-buffer buffer (buffer-string)))
512             (matches (solidity--re-matches (format "%s(.*?):.*?\\([0-9]+\\|infinite\\)" funcname) output 1))
513             (result (car matches)))
514        (kill-buffer buffer)
515        (if (not result)
516          (let* ((clearfilename (file-name-nondirectory filename))
517                 (ctor-matches (solidity--re-matches (format "=.*?%s:%s.*?=" clearfilename funcname) output 0)))
518            (if ctor-matches
519                (message "Gas estimate for '%s' constructor is %s" funcname (car (solidity--re-matches "construction:
520  .*?=.*?\\([0-9]+\\|infinite\\)" output 1)))
521              ;;innermost else
522              (message "No gas estimate found for '%s'" funcname)))
523          ;; outermost else
524          (message "Gas estimate for '%s' is %s" funcname result)))))
525  
526  
527  (defun solidity--start-gasestimate (func)
528    "Start a gas estimation subprocess for FUNC.
529  
530  Does not currently work for constructors."
531    (let* ((filename (buffer-file-name))
532           (command (format "%s --gas %s" solidity-solc-path filename))
533           (process-name (format "solidity-command-%s" command))
534           (process (start-process-shell-command
535                     process-name
536                     (format "*%s*" process-name)
537                     command)))
538      (set-process-query-on-exit-flag process nil)
539      (set-process-sentinel process 'solidity--handle-gasestimate-finish)
540      (process-put process 'solidity-gasestimate-for-function func)
541      (process-put process 'solidity-gasestimate-for-filename filename)))
542  
543  (defun solidity-estimate-gas-at-point ()
544    "Estimate gas of the function at point.
545  
546  Cursor must be at the function's name.  Does not currently work for constructors."
547    (interactive)
548    (solidity--start-gasestimate (thing-at-point 'symbol 'no-properties)))
549  
550  ;;; Support for imenu
551  (defun solidity-mode-imenu-generic-expression ()
552    "Generic expressions for solidity mode imenu."
553    (let* ((spacetabs "[\t\n ]+")
554           (optional-spacetabs "[\t\n ]*")
555           (ident-group "\\([A-Za-z_][A-Za-z0-9_]*\\)")
556           (constructor-ident-group "\\(constructor\\)")
557           (fallback-ident-group "\\(fallback\\)")
558           (modifier (mapconcat 'identity
559                                '("payable" "public" "private" "external" "internal" "view" "pure")
560                                "\\|"))
561           (modifiers (concat "\\(?:\\(?:" modifier "\\)" spacetabs "\\)*")))
562      `(("function", (concat "^" optional-spacetabs "function" spacetabs ident-group) 1)
563        ("modifier", (concat "^" optional-spacetabs "modifier" spacetabs ident-group) 1)
564        ("constructor", (concat "^" optional-spacetabs constructor-ident-group) 1)
565        ("function", (concat "^" optional-spacetabs fallback-ident-group) 1)
566        ("contract", (concat "^" optional-spacetabs "contract" spacetabs ident-group) 1)
567        ("library", (concat "^" optional-spacetabs "library" spacetabs ident-group) 1)
568        ("interface", (concat "^" optional-spacetabs "interface" spacetabs ident-group) 1)
569        )))
570  
571  ;;;###autoload
572  (define-derived-mode solidity-mode c-mode "solidity"
573    "Major mode for editing solidity language buffers."
574    (set-syntax-table solidity-mode-syntax-table)
575    ;; specify syntax highlighting
576    (setq font-lock-defaults '(solidity-font-lock-keywords))
577  
578    ;; register indentation and other langue mode functions, basically the c-mode ones with some modifications
579    (let ((start-value (if (eq solidity-comment-style 'star) "/* " "// "))
580          (end-value (if (eq solidity-comment-style 'star) " */" "")))
581      (set (make-local-variable 'comment-start) start-value)
582      (set (make-local-variable 'comment-end) end-value))
583  
584    (make-local-variable 'comment-start-skip)
585  
586    (make-local-variable 'paragraph-start)
587    (make-local-variable 'paragraph-separate)
588    (make-local-variable 'paragraph-ignore-fill-prefix)
589    (make-local-variable 'adaptive-fill-mode)
590    (make-local-variable 'adaptive-fill-regexp)
591    (make-local-variable 'fill-paragraph-handle-comment)
592  
593    ;; set values for some other variables
594    (set (make-local-variable 'parse-sexp-ignore-comments) t)
595    (set (make-local-variable 'indent-line-function) 'c-indent-line)
596    (set (make-local-variable 'indent-region-function) 'c-indent-region)
597    (set (make-local-variable 'normal-auto-fill-function) 'c-do-auto-fill)
598    (set (make-local-variable 'comment-multi-line) t)
599    (set (make-local-variable 'comment-line-break-function)
600         'c-indent-new-comment-line)
601    (set (make-local-variable 'c-basic-offset) 4)
602  
603    ;; customize indentation more specific to Solidity
604    (make-local-variable 'c-offsets-alist)
605    (add-to-list 'c-offsets-alist '(arglist-close . c-lineup-close-paren))
606  
607    (when solidity-mode-disable-c-mode-hook
608      (set (make-local-variable 'c-mode-hook) nil))
609  
610    ;; set imenu
611    (setq imenu-generic-expression
612          (solidity-mode-imenu-generic-expression))
613  
614    ;; set keymap
615    (use-local-map solidity-mode-map)
616    ;; set hooks
617    (run-hooks 'solidity-mode-hook))
618  
619  
620  (provide 'solidity-mode)
621  ;;; solidity-mode.el ends here