Klimi's new dotfiles with stow.
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

205 行
7.6 KiB

  1. ;;; parseclj-ast.el --- Clojure parser/unparser -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2017-2018 Arne Brasseur
  3. ;; Author: Arne Brasseur <arne@arnebrasseur.net>
  4. ;; This file is not part of GNU Emacs.
  5. ;; This file is free software; you can redistribute it and/or modify
  6. ;; it under the terms of the GNU General Public License as published by
  7. ;; the Free Software Foundation; either version 3, or (at your option)
  8. ;; any later version.
  9. ;; This file is distributed in the hope that it will be useful,
  10. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. ;; GNU General Public License for more details.
  13. ;; You should have received a copy of the GNU General Public License
  14. ;; along with GNU Emacs; see the file COPYING. If not, write to
  15. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  16. ;; Boston, MA 02110-1301, USA.
  17. ;;; Commentary:
  18. ;; Parse Clojure code to an AST, and unparse back to code.
  19. ;;; Code:
  20. (require 'a)
  21. (require 'seq)
  22. (require 'subr-x)
  23. (require 'parseclj-lex)
  24. ;; AST helper functions
  25. (defun parseclj-ast-node (type position &rest attributes)
  26. "Create an AST node with given TYPE and POSITION.
  27. Other ATTRIBUTES can be given as a flat list of key-value pairs."
  28. (apply 'a-list :node-type type :position position attributes))
  29. (defun parseclj-ast-node-p (node)
  30. "Return t if the given NODE is a Clojure AST node."
  31. (and (consp node)
  32. (consp (car node))
  33. (eq :node-type (caar node))))
  34. (defun parseclj-ast-node-attr (node attr)
  35. "Return NODE's ATTR, or nil."
  36. (a-get node attr))
  37. (defun parseclj-ast-node-type (node)
  38. "Return the type of the AST node NODE."
  39. (a-get node :node-type))
  40. (defun parseclj-ast-children (node)
  41. "Return children for the AST NODE."
  42. (a-get node :children))
  43. (defun parseclj-ast-value (node)
  44. "Return the value of NODE as another AST node."
  45. (a-get node :value))
  46. (defun parseclj-ast-leaf-node-p (node)
  47. "Return t if the given ast NODE is a leaf node."
  48. (member (parseclj-ast-node-type node) parseclj-lex--leaf-tokens))
  49. (defun parseclj-ast-branch-node-p (node)
  50. "Return t if the given AST NODE is a branch node."
  51. (not (parseclj-ast-leaf-node-p node)))
  52. ;; Parse/reduce strategy functions
  53. (defun parseclj-ast--reduce-leaf (stack token &optional _options)
  54. "Put into the STACK an AST leaf node based on TOKEN.
  55. Ignores white spaces and comments.
  56. OPTIONS is an association list. See `parseclj-parse' for more information
  57. on available options."
  58. (if (member (parseclj-lex-token-type token) '(:whitespace :comment))
  59. stack
  60. (cons
  61. (parseclj-ast-node (parseclj-lex-token-type token)
  62. (a-get token :pos)
  63. :form (a-get token :form)
  64. :value (parseclj-lex--leaf-token-value token))
  65. stack)))
  66. (defun parseclj-ast--reduce-leaf-with-lexical-preservation (stack token options)
  67. "Put into STACK an AST leaf node based on TOKEN.
  68. This function is very similar to `parseclj-ast--reduce-leaf', but unlike
  69. it, takes into account tokens representing white space or comments and
  70. saves them into the STACK. Nodes produced by this function have a
  71. `:lexical-preservation' key set to t.
  72. OPTIONS is an association list. See `parseclj-parse' for more information
  73. on available options."
  74. (let ((token-type (parseclj-lex-token-type token))
  75. (top (car stack)))
  76. (if (member token-type '(:whitespace :comment))
  77. ;; merge consecutive whitespace or comment tokens
  78. (if (eq token-type (a-get top :node-type))
  79. (cons (a-update top :form #'concat (a-get token :form))
  80. (cdr stack))
  81. (cons (parseclj-ast-node (parseclj-lex-token-type token)
  82. (a-get token :pos)
  83. :form (a-get token :form))
  84. stack))
  85. (parseclj-ast--reduce-leaf stack token options))))
  86. (defun parseclj-ast--reduce-branch (stack opening-token children _options)
  87. "Reduce STACK with an AST branch node representing a collection of elements.
  88. Ignores discard tokens.
  89. OPENING-TOKEN is a lex token representing an opening paren, bracket or
  90. brace.
  91. CHILDREN is the collection of nodes to be reduced into the AST branch node.
  92. OPTIONS is an association list. See `parseclj-parse' for more information
  93. on available options."
  94. (let* ((pos (a-get opening-token :pos))
  95. (type (parseclj-lex-token-type opening-token))
  96. (type (cl-case type
  97. (:lparen :list)
  98. (:lbracket :vector)
  99. (:lbrace :map)
  100. (t type))))
  101. (cl-case type
  102. (:root (cons (parseclj-ast-node :root pos :children children) stack))
  103. (:discard stack)
  104. (:tag (cons (parseclj-ast-node :tag
  105. pos
  106. :tag (intern (substring (a-get opening-token :form) 1))
  107. :children children)
  108. stack))
  109. (:metadata (cons (parseclj-ast-node :with-meta
  110. pos
  111. :children children)
  112. stack))
  113. (:map-prefix (cons (a-assoc (car children)
  114. :map-prefix opening-token)
  115. stack))
  116. (t (cons
  117. (parseclj-ast-node type pos :children children)
  118. stack)))))
  119. (defun parseclj-ast--reduce-branch-with-lexical-preservation (stack opening-token children options)
  120. "Reduce STACK with an AST branch node representing a collection of elements.
  121. Similar to `parseclj-ast--reduce-branch', but reduces discard tokens as
  122. well. Nodes produced by this function have a `:lexical-preservation'
  123. key set to t.
  124. OPENING-TOKEN is a lex token representing an opening paren, bracket or
  125. brace.
  126. CHILDREN is the collection of tokens to be reduced into the AST branch
  127. node.
  128. OPTIONS is an association list. See `parseclj-parse' for more information
  129. on available options."
  130. (if (eq :discard (parseclj-lex-token-type opening-token))
  131. (cons (parseclj-ast-node :discard (a-get opening-token :pos) :children children) stack)
  132. (let* ((stack (funcall #'parseclj-ast--reduce-branch stack opening-token children options))
  133. (top (car stack)))
  134. (if (parseclj-ast-node-p top)
  135. (cons (cl-list* (car top) ;; make sure :node-type remains the first element in the list
  136. '(:lexical-preservation . t)
  137. (cdr top))
  138. (cdr stack))
  139. stack))))
  140. ;; Unparse functions
  141. (declare-function parseclj-unparse-clojure "parseclj")
  142. (defun parseclj-ast--unparse-collection (node)
  143. "Insert a string representation of the given AST branch NODE into buffer."
  144. (let* ((token-type (parseclj-ast-node-type node))
  145. (delimiters (cl-case token-type
  146. (:root (cons "" ""))
  147. (:list (cons "(" ")"))
  148. (:vector (cons "[" "]"))
  149. (:set (cons "#{" "}"))
  150. (:map (cons "{" "}")))))
  151. (insert (car delimiters))
  152. (let ((nodes (alist-get ':children node)))
  153. (when-let (node (car nodes))
  154. (parseclj-unparse-clojure node))
  155. (seq-doseq (child (cdr nodes))
  156. (when (not (a-get node :lexical-preservation))
  157. (insert " "))
  158. (parseclj-unparse-clojure child)))
  159. (insert (cdr delimiters))))
  160. (defun parseclj-ast--unparse-tag (node)
  161. "Insert a string representation of the given AST tag NODE into buffer."
  162. (progn
  163. (insert "#")
  164. (insert (symbol-name (a-get node :tag)))
  165. (insert " ")
  166. (parseclj-unparse-clojure (car (a-get node :children)))))
  167. (provide 'parseclj-ast)
  168. ;;; parseclj-ast.el ends here