/ 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