gdscript.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620
  1. # -*- coding: utf-8 -*-
  2. """
  3. pygments.lexers.gdscript
  4. ~~~~~~~~~~~~~~~~~~~~~~
  5. Lexer for GDScript.
  6. :copyright: Copyright 2xxx by The Godot Engine Community
  7. :license: MIT.
  8. modified by Daniel J. Ramirez <[email protected]> based on the original python.py pygment
  9. further expanded and consolidated with the godot-docs lexer by Zackery R. Smith <[email protected]> and Ste.
  10. """
  11. import re
  12. from pygments.lexer import RegexLexer, include, bygroups, words, combined
  13. from pygments.token import (
  14. Keyword,
  15. Literal,
  16. Name,
  17. Comment,
  18. String,
  19. Number,
  20. Operator,
  21. Whitespace,
  22. Punctuation,
  23. )
  24. __all__ = ["GDScriptLexer"]
  25. class GDScriptLexer(RegexLexer):
  26. """
  27. For GDScript source code.
  28. """
  29. name = "GDScript"
  30. url = "https://www.godotengine.org"
  31. aliases = ["gdscript", "gd"]
  32. filenames = ["*.gd"]
  33. mimetypes = ["text/x-gdscript", "application/x-gdscript"]
  34. @staticmethod
  35. def get_classes(directory: str) -> tuple[str]:
  36. classes = []
  37. with open(f"{directory}/index.rst", "r", encoding="utf-8") as file:
  38. lines = file.readlines()
  39. inside_toctree = False
  40. inside_toctree_body = False
  41. skip_this_block = False
  42. for i, line in enumerate(lines):
  43. stripped = line.strip()
  44. if stripped.startswith(".. toctree::"):
  45. inside_toctree = True
  46. skip_this_block = False
  47. inside_toctree_body = False
  48. continue
  49. if not inside_toctree:
  50. continue
  51. if stripped.startswith(":name:"):
  52. name = stripped.split(":", 2)[-1].strip()
  53. if name in ["toc-class-ref-variants", "toc-class-ref-globals"]:
  54. skip_this_block = True
  55. continue
  56. if skip_this_block or stripped.startswith(":"):
  57. continue
  58. # Avoid skipping the empty line right before the body of the toc
  59. if not inside_toctree_body and stripped == "":
  60. inside_toctree_body = True
  61. continue
  62. if not line.startswith(" ") or stripped.startswith(".. "):
  63. inside_toctree = False
  64. continue
  65. if stripped.startswith("class_"):
  66. # Since everything is lowercase in the index, get the actual casing from the file
  67. with open(f"{directory}/{stripped}.rst", "r", encoding="utf-8") as class_file:
  68. for class_line in class_file:
  69. match = re.match(r"_class_(\w+):", class_line)
  70. if match:
  71. classes.append(match.group(1))
  72. return tuple(classes)
  73. # taken from pygments/gdscript.py
  74. @staticmethod
  75. def inner_string_rules(ttype):
  76. return [
  77. # the old style '%s' % (...) string formatting
  78. (
  79. r"%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?"
  80. "[hlL]?[E-GXc-giorsux%]",
  81. String.Interpol,
  82. ),
  83. # backslashes, quotes, and formatting signs must be parsed one at a time
  84. (r'[^\\\'"%\n]+', ttype),
  85. (r'[\'"\\]', ttype),
  86. # unhandled string formatting sign
  87. (r"%", ttype),
  88. # newlines are an error (use "nl" state)
  89. ]
  90. tokens = {
  91. "whitespace": [(r"\s+", Whitespace)],
  92. "comment": [
  93. (r"##.*$", Comment.Doc),
  94. (r"#(?:end)?region.*$", Comment.Region),
  95. (r"#.*$", Comment.Single),
  96. ],
  97. "punctuation": [
  98. (r"[]{}(),:;[]", Punctuation),
  99. (r":\n", Punctuation),
  100. (r"\\", Punctuation),
  101. ],
  102. # NOTE: from github.com/godotengine/godot-docs
  103. "keyword": [
  104. (
  105. words(
  106. (
  107. # modules/gdscript/gdscript.cpp - GDScriptLanguage::get_reserved_words()
  108. # Declarations.
  109. "class",
  110. "class_name",
  111. "const",
  112. "enum",
  113. "extends",
  114. "func",
  115. "namespace", # Reserved for potential future use.
  116. "signal",
  117. "static",
  118. "trait", # Reserved for potential future use.
  119. "var",
  120. # Other keywords.
  121. "await",
  122. "breakpoint",
  123. "self",
  124. "super",
  125. "yield", # Reserved for potential future use.
  126. # Not really keywords, but used in property syntax.
  127. # also colored like functions, not keywords
  128. #"set",
  129. #"get",
  130. ),
  131. suffix=r"\b",
  132. ),
  133. Keyword,
  134. ),
  135. (
  136. words(
  137. (
  138. # modules/gdscript/gdscript.cpp - GDScriptLanguage::get_reserved_words()
  139. # Control flow.
  140. "break",
  141. "continue",
  142. "elif",
  143. "else",
  144. "for",
  145. "if",
  146. "match",
  147. "pass",
  148. "return",
  149. "when",
  150. "while",
  151. "yield",
  152. ),
  153. suffix=r"\b",
  154. ),
  155. # Custom control flow class used to give control flow keywords a different color,
  156. # like in the Godot editor.
  157. Keyword.ControlFlow,
  158. ),
  159. ],
  160. "builtin": [
  161. (
  162. words(
  163. ("true", "false", "PI", "TAU", "NAN", "INF", "null"),
  164. prefix=r"(?<!\.)",
  165. suffix=r"\b",
  166. ),
  167. Literal,
  168. ),
  169. # NOTE: from github.com/godotengine/godot-docs
  170. (
  171. words(
  172. (
  173. # doc/classes/@GlobalScope.xml
  174. "abs",
  175. "absf",
  176. "absi",
  177. "acos",
  178. "acosh",
  179. "angle_difference",
  180. "asin",
  181. "asinh",
  182. "atan",
  183. "atan2",
  184. "atanh",
  185. "bezier_derivative",
  186. "bezier_interpolate",
  187. "bytes_to_var",
  188. "bytes_to_var_with_objects",
  189. "ceil",
  190. "ceilf",
  191. "ceili",
  192. "clamp",
  193. "clampf",
  194. "clampi",
  195. "cos",
  196. "cosh",
  197. "cubic_interpolate",
  198. "cubic_interpolate_angle",
  199. "cubic_interpolate_angle_in_time",
  200. "cubic_interpolate_in_time",
  201. "db_to_linear",
  202. "deg_to_rad",
  203. "ease",
  204. "error_string",
  205. "exp",
  206. "floor",
  207. "floorf",
  208. "floori",
  209. "fmod",
  210. "fposmod",
  211. "hash",
  212. "instance_from_id",
  213. "inverse_lerp",
  214. "is_equal_approx",
  215. "is_finite",
  216. "is_inf",
  217. "is_instance_id_valid",
  218. "is_instance_valid",
  219. "is_nan",
  220. "is_same",
  221. "is_zero_approx",
  222. "lerp",
  223. "lerp_angle",
  224. "lerpf",
  225. "linear_to_db",
  226. "log",
  227. "max",
  228. "maxf",
  229. "maxi",
  230. "min",
  231. "minf",
  232. "mini",
  233. "move_toward",
  234. "nearest_po2",
  235. "pingpong",
  236. "posmod",
  237. "pow",
  238. "print",
  239. "print_rich",
  240. "print_verbose",
  241. "printerr",
  242. "printraw",
  243. "prints",
  244. "printt",
  245. "push_error",
  246. "push_warning",
  247. "rad_to_deg",
  248. "rand_from_seed",
  249. "randf",
  250. "randf_range",
  251. "randfn",
  252. "randi",
  253. "randi_range",
  254. "randomize",
  255. "remap",
  256. "rid_allocate_id",
  257. "rid_from_int64",
  258. "rotate_toward",
  259. "round",
  260. "roundf",
  261. "roundi",
  262. "seed",
  263. "sign",
  264. "signf",
  265. "signi",
  266. "sin",
  267. "sinh",
  268. "smoothstep",
  269. "snapped",
  270. "snappedf",
  271. "snappedi",
  272. "sqrt",
  273. "step_decimals",
  274. "str",
  275. "str_to_var",
  276. "tan",
  277. "tanh",
  278. "type_convert",
  279. "type_string",
  280. "typeof",
  281. "var_to_bytes",
  282. "var_to_bytes_with_objects",
  283. "var_to_str",
  284. "weakref",
  285. "wrap",
  286. "wrapf",
  287. "wrapi",
  288. # modules/gdscript/doc_classes/@GDScript.xml
  289. "Color8",
  290. "assert",
  291. "char",
  292. "convert",
  293. "dict_to_inst",
  294. "get_stack",
  295. "inst_to_dict",
  296. "is_instance_of",
  297. "len",
  298. "load",
  299. "ord",
  300. "preload",
  301. "print_debug",
  302. "print_stack",
  303. "range",
  304. "type_exists",
  305. ),
  306. prefix=r"(?<!\.)",
  307. suffix=r"\b",
  308. ),
  309. Name.Builtin.Function,
  310. ),
  311. (r"((?<!\.)(self)" r")\b", Name.Builtin.Pseudo),
  312. # NOTE: from github.com/godotengine/godot-docs
  313. (
  314. words(
  315. (
  316. # core/variant/variant.cpp - Variant::get_type_name()
  317. # `Nil` is excluded because it is not allowed in GDScript.
  318. "bool",
  319. "int",
  320. "float",
  321. "String",
  322. "Vector2",
  323. "Vector2i",
  324. "Rect2",
  325. "Rect2i",
  326. "Transform2D",
  327. "Vector3",
  328. "Vector3i",
  329. "Vector4",
  330. "Vector4i",
  331. "Plane",
  332. "AABB",
  333. "Quaternion",
  334. "Basis",
  335. "Transform3D",
  336. "Projection",
  337. "Color",
  338. "RID",
  339. "Object",
  340. "Callable",
  341. "Signal",
  342. "StringName",
  343. "NodePath",
  344. "Dictionary",
  345. "Array",
  346. "PackedByteArray",
  347. "PackedInt32Array",
  348. "PackedInt64Array",
  349. "PackedFloat32Array",
  350. "PackedFloat64Array",
  351. "PackedStringArray",
  352. "PackedVector2Array",
  353. "PackedVector3Array",
  354. "PackedColorArray",
  355. "PackedVector4Array",
  356. # The following are also considered types in GDScript.
  357. "Variant",
  358. "void",
  359. ),
  360. prefix=r"(?<!\.)",
  361. suffix=r"\b",
  362. ),
  363. Name.Builtin.Type,
  364. ),
  365. # copied from https://docs.godotengine.org/en/stable/classes/index.html
  366. (
  367. words(
  368. get_classes("./classes/"),
  369. prefix=r"(?<!\.)",
  370. suffix=r"\b",
  371. ),
  372. Name.Builtin,
  373. ),
  374. # NOTE: from github.com/godotengine/godot-docs
  375. (
  376. words(
  377. (
  378. # modules/gdscript/doc_classes/@GDScript.xml
  379. "@abstract",
  380. "@export",
  381. "@export_category",
  382. "@export_color_no_alpha",
  383. "@export_custom",
  384. "@export_dir",
  385. "@export_enum",
  386. "@export_exp_easing",
  387. "@export_file",
  388. "@export_file_path",
  389. "@export_flags",
  390. "@export_flags_2d_navigation",
  391. "@export_flags_2d_physics",
  392. "@export_flags_2d_render",
  393. "@export_flags_3d_navigation",
  394. "@export_flags_3d_physics",
  395. "@export_flags_3d_render",
  396. "@export_flags_avoidance",
  397. "@export_global_dir",
  398. "@export_global_file",
  399. "@export_group",
  400. "@export_multiline",
  401. "@export_node_path",
  402. "@export_placeholder",
  403. "@export_range",
  404. "@export_storage",
  405. "@export_subgroup",
  406. "@export_tool_button",
  407. "@icon",
  408. "@onready",
  409. "@rpc",
  410. "@static_unload",
  411. "@tool",
  412. "@warning_ignore",
  413. "@warning_ignore_restore",
  414. "@warning_ignore_start",
  415. ),
  416. prefix=r"(?<!\.)",
  417. suffix=r"\b",
  418. ),
  419. Name.Decorator,
  420. ),
  421. ],
  422. "operator": [
  423. (
  424. r"!=|==|<<|>>|&&|\+=|-=|\*=|/=|%=|&=|\|=|\|\||[-~+/*%=<>&^.!|$]",
  425. Operator,
  426. ),
  427. (r"(in|is|and|as|or|not)\b", Operator.Word),
  428. ],
  429. "number": [
  430. (r"([\d_]+\.[\d_]*|[\d_]*\.[\d_]+)([eE][+-]?[\d_]+)?", Number.Float),
  431. (r"[\d_]+[eE][+-]?[\d_]+", Number.Float),
  432. (r"0[xX][a-fA-F\d_]+", Number.Hex),
  433. (r"(-)?0[bB]([01]|(?<=[01])_)+", Number.Bin),
  434. (r"[\d_]+", Number.Integer),
  435. ],
  436. "name": [(r"[a-zA-Z_]\w*", Name)],
  437. "typehint": [
  438. (r"[a-zA-Z_]\w*", Name.Class, "#pop"),
  439. ],
  440. "string_escape": [
  441. (
  442. r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|'
  443. r"U[a-fA-F0-9]{6}|x[a-fA-F0-9]{2}|[0-7]{1,3})",
  444. String.Escape,
  445. )
  446. ],
  447. "string_single": inner_string_rules(String.Single),
  448. "string_double": inner_string_rules(String.Double),
  449. "string_other": inner_string_rules(String.Other),
  450. "string_stringname": inner_string_rules(String.StringName),
  451. "string_nodepath": inner_string_rules(String.NodePath),
  452. "double_quotes": [
  453. (r'"', String.Double, "#pop"),
  454. (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings
  455. include("string_double"),
  456. ],
  457. "single_quotes": [
  458. (r"'", String.Single, "#pop"),
  459. (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings
  460. include("string_single"),
  461. ],
  462. "triple_double_quotes": [
  463. (r'"""', String.Double, "#pop"),
  464. include("string_double"),
  465. include("whitespace"),
  466. ],
  467. "triple_single_quotes": [
  468. (r"'''", String.Single, "#pop"),
  469. include("string_single"),
  470. include("whitespace"),
  471. ],
  472. "node_reference": [
  473. (r'[\$%]"', String.Other, include("node_reference_double")),
  474. (r"[\$%]'", String.Other, include("node_reference_single")),
  475. (r"[\$%][A-Za-z_][\w/]*/?", String.Other),
  476. ],
  477. "node_reference_double": [
  478. (r'"', String.Other, "#pop"),
  479. include("string_other"),
  480. ],
  481. "node_reference_single": [
  482. (r"'", String.Other, "#pop"),
  483. include("string_other"),
  484. ],
  485. "stringname": [
  486. (r'[&]"', String.StringName, include("stringname_double")),
  487. (r"[&]'", String.StringName, include("stringname_single")),
  488. ],
  489. "stringname_double": [
  490. (r'"', String.StringName, "#pop"),
  491. include("string_stringname"),
  492. ],
  493. "stringname_single": [
  494. (r"'", String.StringName, "#pop"),
  495. include("string_stringname"),
  496. ],
  497. "nodepath": [
  498. (r'[\^]"', String.NodePath, include("nodepath_double")),
  499. (r"[\^]'", String.NodePath, include("nodepath_single")),
  500. ],
  501. "nodepath_double": [
  502. (r'"', String.NodePath, "#pop"),
  503. include("string_nodepath"),
  504. ],
  505. "nodepath_single": [
  506. (r"'", String.NodePath, "#pop"),
  507. include("string_nodepath"),
  508. ],
  509. "function_name": [(r"[a-zA-Z_]\w*", Name.Function.Declaration, "#pop")],
  510. "enum_name": [(r"[a-zA-Z_]\w*", Name, "#pop")],
  511. "function": [
  512. (r"\b([a-zA-Z_]\w*)\s*(?=\()", Name.Function),
  513. (
  514. # colored like functions, even without braces
  515. words(("set", "get",), suffix=r"\b", ),
  516. Name.Function,
  517. ),
  518. ],
  519. #######################################################################
  520. # LEXER ENTRY POINT
  521. #######################################################################
  522. "root": [
  523. include("whitespace"),
  524. include("comment"),
  525. include("punctuation"),
  526. include("builtin"),
  527. # strings
  528. include("stringname"),
  529. include("nodepath"),
  530. include("node_reference"),
  531. (
  532. '(r)(""")',
  533. bygroups(String.Affix, String.Double),
  534. "triple_double_quotes",
  535. ),
  536. (
  537. "(r)(''')",
  538. bygroups(String.Affix, String.Single),
  539. "triple_single_quotes",
  540. ),
  541. (
  542. '(r)(")',
  543. bygroups(String.Affix, String.Double),
  544. "double_quotes",
  545. ),
  546. (
  547. "(r)(')",
  548. bygroups(String.Affix, String.Single),
  549. "single_quotes",
  550. ),
  551. (
  552. '(r?)(""")',
  553. bygroups(String.Affix, String.Double),
  554. combined("string_escape", "triple_double_quotes"),
  555. ),
  556. (
  557. "(r?)(''')",
  558. bygroups(String.Affix, String.Single),
  559. combined("string_escape", "triple_single_quotes"),
  560. ),
  561. (
  562. '(r?)(")',
  563. bygroups(String.Affix, String.Double),
  564. combined("string_escape", "double_quotes"),
  565. ),
  566. (
  567. "(r?)(')",
  568. bygroups(String.Affix, String.Single),
  569. combined("string_escape", "single_quotes"),
  570. ),
  571. # consider Name after a . as instance/members variables
  572. (r"(?<!\.)(\.)([a-zA-Z_]\w*)\b(?!\s*\()", bygroups(Operator, Name.Variable.Instance)),
  573. include("operator"),
  574. # Lookahead to not match the start of function_name to dodge errors on nameless lambdas
  575. (r"(func)(\s+)(?=[a-zA-Z_])", bygroups(Keyword, Whitespace), "function_name"),
  576. (r"(enum)(\s+)(?=[a-zA-Z_])", bygroups(Keyword, Whitespace), "enum_name"),
  577. include("keyword"),
  578. include("function"),
  579. # NOTE:
  580. # This matches all PascalCase as a class. If this raises issues
  581. # please report it.
  582. # see: https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/gdscript_styleguide.html#naming-conventions
  583. #(r"\s*([A-Z][a-zA-Z0-9_]*)", Name.Class),
  584. # Only PascalCase, but exclude SCREAMING_SNAKE for constants
  585. (r"\b([A-Z][a-z0-9]+(?:[A-Z][a-z0-9]+)*)\b", Name.Class),
  586. include("name"),
  587. include("number"),
  588. ],
  589. }
  590. def setup(sphinx):
  591. sphinx.add_lexer("gdscript", GDScriptLexer)
  592. return {
  593. "parallel_read_safe": True,
  594. "parallel_write_safe": True,
  595. }