This commit is contained in:
2026-05-21 11:17:30 +08:00
commit d01d150f36
4 changed files with 779 additions and 0 deletions

683
vendor/c3-ts-mode/c3-ts-mode.el vendored Normal file
View File

@@ -0,0 +1,683 @@
;;; c3-ts-mode.el --- Major mode for C3 using tree-sitter -*- lexical-binding: t -*-
;; Author: Christian Buttner <https://github.com/cbuttner>
;; URL: https://github.com/c3lang/c3-ts-mode
;; Keywords: c3 languages tree-sitter
;; Version: 0.9.0
;; Package-Requires : ((emacs "29.1"))
;;; License:
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, see <https://www.gnu.org/licenses>.
;;; Commentary:
;; This tree-sitter powered Emacs 29+ major mode provides syntax highlighting, indentation, imenu and which-function support for C3.
;; It's built against the tree-sitter grammar located at <https://github.com/c3lang/tree-sitter-c3>.
;;; Code:
(require 'treesit)
(require 'c-ts-common)
(require 'compile)
(eval-when-compile (require 'rx))
(declare-function treesit-node-child-by-field-name "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-node-text "treesit.c")
(declare-function treesit-parser-create "treesit.c")
(defgroup c3-ts nil
"Major mode for editing C3 files."
:prefix "c3-ts-"
:group 'languages)
(defcustom c3-ts-mode-indent-offset 2
"Number of spaces for each indentation step in `c3-ts-mode'."
:version "29.1"
:type 'integer
:safe 'integerp
:group 'c3-ts)
(defcustom c3-ts-mode-highlight-variable 't
"Enable highlighting of variables in `c3-ts-mode'."
:version "29.1"
:type 'boolean
:safe 'booleanp
:group 'c3-ts)
(defcustom c3-ts-mode-highlight-property 't
"Enable highlighting of members in `c3-ts-mode'."
:version "29.1"
:type 'boolean
:safe 'booleanp
:group 'c3-ts)
(defcustom c3-ts-mode-highlight-assignment 't
"Enable highlighting of assignments in `c3-ts-mode'."
:version "29.1"
:type 'boolean
:safe 'booleanp
:group 'c3-ts)
(defcustom c3-ts-mode-highlight-punctuation 't
"Enable highlighting of punctuation in `c3-ts-mode'."
:version "29.1"
:type 'boolean
:safe 'booleanp
:group 'c3-ts)
(defcustom c3-ts-mode-module-path-face '@font-lock-constant-face
"The face to use for highlighting module paths in `c3-ts-mode'."
:version "29.1"
:type 'symbol
:group 'c3-ts)
(defcustom c3-ts-mode-assignment-face '@font-lock-variable-name-face
"The face to use for highlighting assignments in `c3-ts-mode'."
:version "29.1"
:type 'symbol
:group 'c3-ts)
(defcustom c3-ts-mode-hook nil
"Hook run after entering `c3-ts-mode'."
:version "29.1"
:type 'hook
:group 'c3-ts)
(defvar c3-ts-mode--syntax-table
(let ((table (make-syntax-table)))
;; Adapted from c-ts-mode.el
(modify-syntax-entry ?_ "_" table)
(modify-syntax-entry ?\\ "\\" table)
(modify-syntax-entry ?+ "." table)
(modify-syntax-entry ?- "." table)
(modify-syntax-entry ?= "." table)
(modify-syntax-entry ?% "." table)
(modify-syntax-entry ?< ". 1" table) ; C3: the first character of a comment-start sequence
(modify-syntax-entry ?> ". 4" table) ; C3: the second character of a comment-end sequence
(modify-syntax-entry ?@ "w" table) ; C3: identifiers starting with @
(modify-syntax-entry ?& "." table)
(modify-syntax-entry ?| "." table)
(modify-syntax-entry ?\' "\"" table)
(modify-syntax-entry ?` "\"" table)
(modify-syntax-entry ?\240 "." table)
(modify-syntax-entry ?/ ". 124b" table)
(modify-syntax-entry ?* ". 23" table)
(modify-syntax-entry ?\n "> b" table)
(modify-syntax-entry ?\^m "> b" table)
table)
"Syntax table for `c3-ts-mode'.")
(defvar c3-ts-mode--keywords
;; From "c3c --list-keywords", without base types
'("alias"
"assert"
"asm"
"attrdef"
"bitstruct"
"break"
"case"
"catch"
"const"
"continue"
"default"
"defer"
"do"
"else"
"enum"
"extern"
;; "false" ;; NOTE Treated as constant
"faultdef"
"for"
"foreach"
"foreach_r"
"fn"
"tlocal"
"if"
"inline"
"import"
"macro"
"module"
"nextcase"
;; "null" ;; NOTE Treated as constant
"interface"
"return"
"static"
"struct"
"switch"
;; "true" ;; NOTE Treated as constant
"try"
"typedef"
"union"
"var"
"while"
"$alignof"
"$assert"
"$assignable"
"$case"
"$default"
"$defined"
"$echo"
"$else"
"$embed"
"$endfor"
"$endforeach"
"$endif"
"$endswitch"
"$eval"
"$evaltype"
"$error"
"$exec"
"$extnameof"
"$feature"
"$for"
"$foreach"
"$if"
"$include"
"$is_const"
"$kindof"
"$nameof"
"$offsetof"
"$qnameof"
"$sizeof"
"$stringify"
"$switch"
"$typefrom"
"$typeof"
"$vacount"
"$vatype"
"$vaconst"
"$vaarg"
"$vaexpr"
"$vasplat"))
(defvar c3-ts-mode--type-properties
;; From "c3c --list-type-properties"
'("alignof"
"associated"
"elements"
"extnameof"
"from_ordinal"
"get"
"inf"
"is_eq"
"is_ordered"
"is_substruct"
"len"
"lookup"
"lookup_field"
"max"
"membersof"
"methodsof"
"min"
"nan"
"inner"
"kindof"
"names"
"nameof"
"params"
"paramsof"
"parentof"
"qnameof"
"returns"
"sizeof"
"set"
"tagof"
"has_tagof"
"values"
;; Separate token in c3c
"typeid"))
(defvar c3-ts-mode--operators
;; From "c3c --list-operators"
'("&"
"!"
"~"
"|"
"^"
":"
;; ","
;; ";"
"="
">"
"/"
"."
;; "#"
"<"
;; "{"
;; "["
;; "("
"-"
"%"
"+"
"?"
;; "}"
;; "]"
;; ")"
"*"
;; "_"
"&&"
;; "->"
"!!"
"&="
"|="
"^="
"/="
".."
"?:"
"=="
">="
"=>"
"<="
;; "[<"
"-="
"--"
"%="
"*="
"!="
"||"
"+="
"++"
;; ">]"
"??"
;; "::"
"<<"
">>"
"..."
"<<="
">>="
"&&&"
"+++"
"|||"
"???"
"+++="))
(defvar c3-ts-mode--feature-list
`((comment definition)
(keyword string type)
;; TODO Not clear if assignment should go in level 4 or not (3 is the default level).
,(append
'(builtin attribute escape-sequence literal constant assembly module function doc-comment)
(when c3-ts-mode-highlight-assignment '(assignment)))
,(append
'(type-property operator bracket)
(when c3-ts-mode-highlight-punctuation '(punctuation))
(when c3-ts-mode-highlight-variable '(variable))
(when c3-ts-mode-highlight-property '(property)))
;; (error) ;; Disabled by default
)
"`treesit-font-lock-feature-list' for `c3-ts-mode'.")
(defvar c3-ts-mode--font-lock-settings
;; NOTE Earlier rules have precedence over later rules
(treesit-font-lock-rules
:language 'c3
:feature 'doc-comment
'((doc_comment_contract name: (at_ident) @font-lock-builtin-face)
(doc_comment_contract mutability_contract: (_) @font-lock-constant-face)
(doc_comment ["<*" "*>"] @font-lock-doc-face)
(doc_comment_text) @font-lock-doc-face
;; (doc_comment_contract parameter: ([(ident) (ct_ident) (hash_ident)]) @font-lock-variable-name-face)
(doc_comment_contract [":" "?"] @font-lock-doc-face)
(doc_comment_contract description: (_) @font-lock-doc-face)
)
:language 'c3
:feature 'comment
'((line_comment) @font-lock-comment-face
(block_comment) @font-lock-comment-face
(doc_comment) @font-lock-doc-face)
:language 'c3
:feature 'literal
'((integer_literal) @font-lock-number-face
(real_literal) @font-lock-number-face
(char_literal) @font-lock-constant-face
(bytes_literal) @font-lock-constant-face)
:language 'c3
:feature 'string
'((string_literal) @font-lock-string-face
(raw_string_literal) @font-lock-string-face)
:language 'c3
:feature 'escape-sequence
:override t
'((escape_sequence) @font-lock-escape-face)
:language 'c3
:feature 'keyword
`([,@c3-ts-mode--keywords] @font-lock-keyword-face)
:language 'c3
:feature 'builtin
'([(builtin) (builtin_const)] @font-lock-builtin-face)
:language 'c3
:feature 'type-property
`((type_access_expr (access_ident (ident) @font-lock-constant-face (:match ,(rx-to-string `(: bos (or ,@c3-ts-mode--type-properties) eos)) @font-lock-constant-face))))
:language 'c3
:feature 'constant
'((const_ident) @font-lock-constant-face
["true" "false" "null"] @font-lock-constant-face)
:language 'c3
:feature 'assembly
'((asm_instr [(ident) "int"] @font-lock-function-call-face)
(asm_expr [(ct_ident) (ct_const_ident)] @font-lock-variable-use-face))
:language 'c3
:feature 'module
`((module_resolution (ident) ,c3-ts-mode-module-path-face)
(module_declaration (path_ident (ident) ,c3-ts-mode-module-path-face))
(import_path (path_ident (ident) ,c3-ts-mode-module-path-face)))
:language 'c3
:feature 'attribute
'((attribute name: (at_ident) @font-lock-builtin-face)
(at_type_ident) @font-lock-builtin-face
(call_inline_attributes (at_ident) @font-lock-builtin-face)
(type_suffix (at_ident) @font-lock-builtin-face))
:language 'c3
:feature 'type
'((type_ident) @font-lock-type-face
(ct_type_ident) @font-lock-type-face
(base_type_name) @font-lock-type-face
;; TODO Probably don't want these
;; (type_suffix ["[" "[<" ">]" "]"] @font-lock-type-face)
;; (type "!" @font-lock-type-face :anchor)
)
:language 'c3
:feature 'definition
'((func_header name: (_) @font-lock-function-name-face)
(macro_header name: (_) @font-lock-function-name-face))
:language 'c3
:feature 'function
'((call_expr function: (ident_expr [(ident) (at_ident)] @font-lock-function-call-face))
(call_expr function: (trailing_generic_expr argument: (ident_expr [(ident) (at_ident)] @font-lock-function-call-face)))
;; Method call
(call_expr function: (field_expr field: (access_ident [(ident) (at_ident)] @font-lock-function-call-face))) ; NOTE Ambiguous, could be calling a method or function pointer
;; Method on type
(call_expr function: (type_access_expr field: (access_ident [(ident) (at_ident)] @font-lock-function-call-face))))
:language 'c3
:feature 'assignment
`((assignment_expr left: (ident_expr (ident) ,c3-ts-mode-assignment-face))
(assignment_expr left: (field_expr field: (_) ,c3-ts-mode-assignment-face))
(assignment_expr left: (unary_expr operator: "*" ,c3-ts-mode-assignment-face))
(assignment_expr left: (subscript_expr ["[" "]"] ,c3-ts-mode-assignment-face))
(assignment_expr left: (maybe_deref_expr ["[" "]"] ,c3-ts-mode-assignment-face))
(update_expr argument: (ident_expr (ident) ,c3-ts-mode-assignment-face))
(update_expr argument: (field_expr field: (_) ,c3-ts-mode-assignment-face))
(update_expr argument: (unary_expr operator: "*" ,c3-ts-mode-assignment-face))
(update_expr argument: (subscript_expr ["[" "]"] ,c3-ts-mode-assignment-face))
(unary_expr operator: ["--" "++"] argument: (ident_expr (ident) ,c3-ts-mode-assignment-face))
(unary_expr operator: ["--" "++"] argument: (field_expr field: (access_ident (ident)) ,c3-ts-mode-assignment-face))
(unary_expr operator: ["--" "++"] argument: (subscript_expr ["[" "]"] ,c3-ts-mode-assignment-face)))
:language 'c3
:feature 'operator
`(([,@c3-ts-mode--operators]) @font-lock-operator-face)
:language 'c3
:feature 'property
'(;; Member
(field_expr field: (access_ident (ident) @font-lock-property-use-face))
(struct_member_declaration (ident) @font-lock-property-name-face)
(struct_member_declaration (identifier_list (ident) @font-lock-property-name-face))
(bitstruct_member_declaration (ident) @font-lock-property-name-face)
(initializer_list (initializer_element (param_path (param_path_element (access_ident (ident) @font-lock-property-name-face))))))
:language 'c3
:feature 'variable
'([(ident) (ct_ident)] @font-lock-variable-use-face
;; Parameter
(param name: (_) @font-lock-variable-name-face)
(trailing_block_param (at_ident) @font-lock-variable-name-face)
(call_arg_list (call_arg name: (_) @font-lock-variable-name-face))
(enum_param (ident) @font-lock-variable-name-face)
;; Declaration
(declaration (identifier_list [(ident) (ct_ident)] @font-lock-variable-name-face))
(declaration name: [(ident) (ct_ident)] @font-lock-variable-name-face)
(var_declaration name: [(ident) (ct_ident)] @font-lock-variable-name-face)
(try_unwrap (ident) @font-lock-variable-name-face)
(catch_unwrap (ident) @font-lock-variable-name-face))
:language 'c3
:feature 'bracket
'((["(" ")" "[" "]" "{" "}" "[<" ">]"]) @font-lock-bracket-face)
:language 'c3
:feature 'punctuation
'(([";" "," "::"]) @font-lock-punctuation-face)
:language 'c3
:feature 'error
:override t
'((ERROR) @font-lock-warning-face))
"Tree-sitter font-lock settings for `c3-ts-mode'.")
(defvar c3-ts-mode--simple-indent-rules
`((c3
((parent-is "source_file") column-0 0)
((node-is "else") parent-bol 0) ;; Also matches $else
((node-is "^\\(case\\|ct_case\\|default\\|ct_default\\)_stmt$") parent-bol c3-ts-mode-indent-offset)
((node-is "ct_stmt_body") parent-bol c3-ts-mode-indent-offset)
((parent-is "ct_stmt_body") standalone-parent 0)
((node-is "$endif") parent-bol 0)
((node-is "$endfor") parent-bol 0)
((node-is "$endswitch") parent-bol 0)
((and (parent-is "block_comment_text") c-ts-common-looking-at-star)
c-ts-common-comment-start-after-first-star -1)
;; NOTE This only indents the first line of a doc comment text. This way we preserve identation on subsequent lines, such as list item indentation. TODO Indent to a minimum of 1? Can still ruin formatting
((node-is "doc_comment_text") parent-bol 1)
((node-is "doc_comment_contract") parent-bol 1)
((node-is "*>") parent-bol 0)
((node-is "}") standalone-parent 0)
((node-is ")") standalone-parent 0)
((match "while" "do_stmt" nil nil nil) standalone-parent 0)
((match "compound_stmt" "else_part" nil nil nil) standalone-parent 0)
;; Attributes
((node-is "attributes") standalone-parent c3-ts-mode-indent-offset)
((parent-is "attributes") parent 0) ;; Align attributes on same column
;; Body/block children
((parent-is "enum_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "struct_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "bitstruct_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "interface_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "macro_func_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "implies_body") standalone-parent c3-ts-mode-indent-offset)
((parent-is "compound_stmt") standalone-parent c3-ts-mode-indent-offset)
((parent-is "initializer_list") standalone-parent c3-ts-mode-indent-offset)
((parent-is "case_stmt") standalone-parent c3-ts-mode-indent-offset)
((parent-is "default_stmt") standalone-parent c3-ts-mode-indent-offset)
;; if/switch/for/while/defer with block
((match "compound_stmt" "\\(if\\|switch\\|for\\|foreach\\|while\\|do\\|defer\\)_stmt" nil nil nil) standalone-parent 0)
;; for/while/defer without block
((parent-is "\\(for\\|foreach\\|while\\|defer\\)_stmt") standalone-parent c3-ts-mode-indent-offset)
;; Body not handled so far
((field-is "body") standalone-parent 0)
((field-is "lambda_body") standalone-parent 0)
;; Trailing macro block
((match "compound_stmt" nil "trailing" nil nil) parent 0)
((match nil "field_expr" "field" nil nil) parent-bol c3-ts-mode-indent-offset)
((n-p-gp "." "field_expr" nil) parent-bol c3-ts-mode-indent-offset) ;; Field access beginning with "."
((match nil "type_access_expr" "field" nil nil) parent-bol c3-ts-mode-indent-offset)
((match nil "assignment_expr" "right" nil nil) parent-bol c3-ts-mode-indent-offset)
((match nil "const_declaration" "right" nil nil) parent-bol c3-ts-mode-indent-offset)
((match nil "declaration" "right" nil nil) parent-bol c3-ts-mode-indent-offset)
;; Multi line declarations
((parent-is "identifier_list") parent 0)
((parent-is "^for_cond") parent 1)
((parent-is "^foreach_cond") parent 1)
((parent-is "ct_for_cond") parent 0)
((parent-is "ct_foreach_cond") parent 0)
((parent-is "paren_cond") parent 1)
((parent-is "catch_unwrap_list") parent 0)
((parent-is "comma_decl_or_expr") parent 0)
;; First parameter/argument
((match "^\\(call_\\|attribute_\\)?arg\\|param\\|enum_param_declaration\\|trailing_block_param$" nil nil 1 1) parent-bol c3-ts-mode-indent-offset)
;; Subsequent params/arguments
((match "^\\(call_\\|attribute_\\)?arg\\|param\\|enum_param_declaration\\|trailing_block_param$" nil nil 2 nil) (nth-sibling 1) 0)
;; String/bytes literals
((node-is "raw_string_literal") no-indent)
((match "string_literal" "string_expr" nil 0 0) parent 0)
((match "string_literal" "string_expr" nil 1 nil) (nth-sibling 0) 0)
((match "bytes_literal" "bytes_expr" nil 0 0) parent 0)
((match "bytes_literal" "bytes_expr" nil 1 nil) (nth-sibling 0) 0)
((parent-is "paren") parent 1)
((parent-is "binary") parent 0)
((parent-is "range") parent 0)
((parent-is "elvis_orelse") parent 0)
((parent-is "ternary") parent c3-ts-mode-indent-offset)
((parent-is "subscript") parent c3-ts-mode-indent-offset)
((parent-is "update") parent c3-ts-mode-indent-offset)
((parent-is "call") parent c3-ts-mode-indent-offset)
((parent-is "cast") parent c3-ts-mode-indent-offset)
((parent-is "func_declaration") standalone-parent 0)
((parent-is "func_header") standalone-parent 0)
((parent-is "macro_declaration") standalone-parent 0)
((parent-is "macro_header") standalone-parent 0)
((node-is "initializer_list") parent 0)
;; ((parent-is "ERROR") no-indent 0)
)))
(defun c3-ts-mode--defun-name (node)
"Return the name of the defun NODE."
(let ()
(treesit-node-text
(treesit-node-child-by-field-name
(pcase (treesit-node-type node)
("func_definition"
(car
(treesit-filter-child node
(lambda (ch)
(string-equal (treesit-node-type ch) "func_header"))
t)))
("macro_declaration"
(car
(treesit-filter-child node
(lambda (ch)
(string-equal (treesit-node-type ch) "macro_header"))
t)))
(_ node))
"name")
t)))
;;;###autoload
(define-derived-mode c3-ts-mode prog-mode "C3"
"Major mode for editing C3 files, powered by tree-sitter."
:group 'c3-ts
:syntax-table c3-ts-mode--syntax-table
;; Comment
(c-ts-common-comment-setup)
;; Electric
(setq-local electric-indent-chars
(append "{}():;," electric-indent-chars))
(when (treesit-ready-p 'c3)
(treesit-parser-create 'c3)
;; Font-lock
(setq-local treesit-font-lock-settings c3-ts-mode--font-lock-settings)
(setq-local treesit-font-lock-feature-list c3-ts-mode--feature-list)
;; Indent
(setq-local treesit-simple-indent-rules c3-ts-mode--simple-indent-rules)
;; Navigation
(setq-local treesit-defun-name-function #'c3-ts-mode--defun-name)
(setq-local treesit-defun-type-regexp
(rx bos
(or "struct_declaration"
"bitstruct_declaration"
"enum_declaration"
"interface_declaration"
"func_definition"
"macro_declaration"
"const_declaration"
"alias_declaration"
"typedef_declaration"
"faultdef_declaration"
"attrdef_declaration")
eos))
;; Imenu
(setq-local treesit-simple-imenu-settings
`(("Struct" "\\`struct_declaration\\'" nil nil)
("Bitstruct" "\\`bitstruct_declaration\\'" nil nil)
("Enum" "\\`enum_declaration\\'" nil nil)
("Interface" "\\`interface_declaration\\'" nil nil)
("Function" "\\`func_definition\\'" nil nil)
("Macro" "\\`macro_declaration\\'" nil nil)
("Const" "\\`const_declaration\\'" nil nil)
("Alias" "\\`alias_declaration\\'" nil nil)
("Type" "\\`typedef_declaration\\'" nil nil)
("Fault" "\\`faultdef_declaration\\'" nil nil)
("Attribute" "\\`attrdef_declaration\\'" nil nil)))
;; Which-function
(setq-local which-func-functions (treesit-defun-at-point))
(treesit-major-mode-setup)))
(when (treesit-ready-p 'c3)
(add-to-list 'auto-mode-alist '("\\.c3[it]?\\'" . c3-ts-mode)))
(eval-after-load 'compile
(lambda()
(add-to-list 'compilation-error-regexp-alist-alist
'(c3
"^(\\([^:]*\\):\\([0-9]+\\):\\([0-9]+\\)) \\(Warning\\)?.*$"
1 2 3 (4)))
(add-to-list 'compilation-error-regexp-alist 'c3)))
(provide 'c3-ts-mode)
;;; c3-ts-mode.el ends here