# -*- coding: utf-8 -*- """ pygments.lexers.gdscript ~~~~~~~~~~~~~~~~~~~~~~ Lexer for GDScript. :copyright: Copyright 2xxx by The Godot Engine Community :license: MIT. modified by Daniel J. Ramirez based on the original python.py pygment further expanded and consolidated with the godot-docs lexer by Zackery R. Smith and Ste. """ import re from pygments.lexer import RegexLexer, include, bygroups, words, combined from pygments.token import ( Keyword, Literal, Name, Comment, String, Number, Operator, Whitespace, Punctuation, ) __all__ = ["GDScriptLexer"] class GDScriptLexer(RegexLexer): """ For GDScript source code. """ name = "GDScript" url = "https://www.godotengine.org" aliases = ["gdscript", "gd"] filenames = ["*.gd"] mimetypes = ["text/x-gdscript", "application/x-gdscript"] @staticmethod def get_classes(directory: str) -> tuple[str]: classes = [] with open(f"{directory}/index.rst", "r", encoding="utf-8") as file: lines = file.readlines() inside_toctree = False inside_toctree_body = False skip_this_block = False for i, line in enumerate(lines): stripped = line.strip() if stripped.startswith(".. toctree::"): inside_toctree = True skip_this_block = False inside_toctree_body = False continue if not inside_toctree: continue if stripped.startswith(":name:"): name = stripped.split(":", 2)[-1].strip() if name in ["toc-class-ref-variants", "toc-class-ref-globals"]: skip_this_block = True continue if skip_this_block or stripped.startswith(":"): continue # Avoid skipping the empty line right before the body of the toc if not inside_toctree_body and stripped == "": inside_toctree_body = True continue if not line.startswith(" ") or stripped.startswith(".. "): inside_toctree = False continue if stripped.startswith("class_"): # Since everything is lowercase in the index, get the actual casing from the file with open(f"{directory}/{stripped}.rst", "r", encoding="utf-8") as class_file: for class_line in class_file: match = re.match(r"_class_(\w+):", class_line) if match: classes.append(match.group(1)) return tuple(classes) # taken from pygments/gdscript.py @staticmethod def inner_string_rules(ttype): return [ # the old style '%s' % (...) string formatting ( r"%(\(\w+\))?[-#0 +]*([0-9]+|[*])?(\.([0-9]+|[*]))?" "[hlL]?[E-GXc-giorsux%]", String.Interpol, ), # backslashes, quotes, and formatting signs must be parsed one at a time (r'[^\\\'"%\n]+', ttype), (r'[\'"\\]', ttype), # unhandled string formatting sign (r"%", ttype), # newlines are an error (use "nl" state) ] tokens = { "whitespace": [(r"\s+", Whitespace)], "comment": [ (r"##.*$", Comment.Doc), (r"#(?:end)?region.*$", Comment.Region), (r"#.*$", Comment.Single), ], "punctuation": [ (r"[]{}(),:;[]", Punctuation), (r":\n", Punctuation), (r"\\", Punctuation), ], # NOTE: from github.com/godotengine/godot-docs "keyword": [ ( words( ( # modules/gdscript/gdscript.cpp - GDScriptLanguage::get_reserved_words() # Declarations. "class", "class_name", "const", "enum", "extends", "func", "namespace", # Reserved for potential future use. "signal", "static", "trait", # Reserved for potential future use. "var", # Other keywords. "await", "breakpoint", "self", "super", "yield", # Reserved for potential future use. # Not really keywords, but used in property syntax. # also colored like functions, not keywords #"set", #"get", ), suffix=r"\b", ), Keyword, ), ( words( ( # modules/gdscript/gdscript.cpp - GDScriptLanguage::get_reserved_words() # Control flow. "break", "continue", "elif", "else", "for", "if", "match", "pass", "return", "when", "while", "yield", ), suffix=r"\b", ), # Custom control flow class used to give control flow keywords a different color, # like in the Godot editor. Keyword.ControlFlow, ), ], "builtin": [ ( words( ("true", "false", "PI", "TAU", "NAN", "INF", "null"), prefix=r"(?>|&&|\+=|-=|\*=|/=|%=|&=|\|=|\|\||[-~+/*%=<>&^.!|$]", Operator, ), (r"(in|is|and|as|or|not)\b", Operator.Word), ], "number": [ (r"([\d_]+\.[\d_]*|[\d_]*\.[\d_]+)([eE][+-]?[\d_]+)?", Number.Float), (r"[\d_]+[eE][+-]?[\d_]+", Number.Float), (r"0[xX][a-fA-F\d_]+", Number.Hex), (r"(-)?0[bB]([01]|(?<=[01])_)+", Number.Bin), (r"[\d_]+", Number.Integer), ], "name": [(r"[a-zA-Z_]\w*", Name)], "typehint": [ (r"[a-zA-Z_]\w*", Name.Class, "#pop"), ], "string_escape": [ ( r'\\([\\abfnrtv"\']|\n|N\{.*?\}|u[a-fA-F0-9]{4}|' r"U[a-fA-F0-9]{6}|x[a-fA-F0-9]{2}|[0-7]{1,3})", String.Escape, ) ], "string_single": inner_string_rules(String.Single), "string_double": inner_string_rules(String.Double), "string_other": inner_string_rules(String.Other), "string_stringname": inner_string_rules(String.StringName), "string_nodepath": inner_string_rules(String.NodePath), "double_quotes": [ (r'"', String.Double, "#pop"), (r'\\\\|\\"|\\\n', String.Escape), # included here for raw strings include("string_double"), ], "single_quotes": [ (r"'", String.Single, "#pop"), (r"\\\\|\\'|\\\n", String.Escape), # included here for raw strings include("string_single"), ], "triple_double_quotes": [ (r'"""', String.Double, "#pop"), include("string_double"), include("whitespace"), ], "triple_single_quotes": [ (r"'''", String.Single, "#pop"), include("string_single"), include("whitespace"), ], "node_reference": [ (r'[\$%]"', String.Other, include("node_reference_double")), (r"[\$%]'", String.Other, include("node_reference_single")), (r"[\$%][A-Za-z_][\w/]*/?", String.Other), ], "node_reference_double": [ (r'"', String.Other, "#pop"), include("string_other"), ], "node_reference_single": [ (r"'", String.Other, "#pop"), include("string_other"), ], "stringname": [ (r'[&]"', String.StringName, include("stringname_double")), (r"[&]'", String.StringName, include("stringname_single")), ], "stringname_double": [ (r'"', String.StringName, "#pop"), include("string_stringname"), ], "stringname_single": [ (r"'", String.StringName, "#pop"), include("string_stringname"), ], "nodepath": [ (r'[\^]"', String.NodePath, include("nodepath_double")), (r"[\^]'", String.NodePath, include("nodepath_single")), ], "nodepath_double": [ (r'"', String.NodePath, "#pop"), include("string_nodepath"), ], "nodepath_single": [ (r"'", String.NodePath, "#pop"), include("string_nodepath"), ], "function_name": [(r"[a-zA-Z_]\w*", Name.Function.Declaration, "#pop")], "enum_name": [(r"[a-zA-Z_]\w*", Name, "#pop")], "function": [ (r"\b([a-zA-Z_]\w*)\s*(?=\()", Name.Function), ( # colored like functions, even without braces words(("set", "get",), suffix=r"\b", ), Name.Function, ), ], ####################################################################### # LEXER ENTRY POINT ####################################################################### "root": [ include("whitespace"), include("comment"), include("punctuation"), include("builtin"), # strings include("stringname"), include("nodepath"), include("node_reference"), ( '(r)(""")', bygroups(String.Affix, String.Double), "triple_double_quotes", ), ( "(r)(''')", bygroups(String.Affix, String.Single), "triple_single_quotes", ), ( '(r)(")', bygroups(String.Affix, String.Double), "double_quotes", ), ( "(r)(')", bygroups(String.Affix, String.Single), "single_quotes", ), ( '(r?)(""")', bygroups(String.Affix, String.Double), combined("string_escape", "triple_double_quotes"), ), ( "(r?)(''')", bygroups(String.Affix, String.Single), combined("string_escape", "triple_single_quotes"), ), ( '(r?)(")', bygroups(String.Affix, String.Double), combined("string_escape", "double_quotes"), ), ( "(r?)(')", bygroups(String.Affix, String.Single), combined("string_escape", "single_quotes"), ), # consider Name after a . as instance/members variables (r"(?