Preprocessor for godot 4.5 gdscript. Adds some nice features that aren't possible in gdscript.
constexprkeyword for variables that can have their values computed at compile time and are then made const for use in gdscript. The expression after the = operator in a constexpr variable declaration gets evaluated as a godot Expression. So any code that is a valid Expression can go there and anything in @GlobalScope can be used in a constant expression.constevalkeyword that works the same as constexpr variables except the value will instead be inlined directly everywhere it is used. This way you can precompute values at compile time and also have them not use any memory at run time. consteval variables will not exist as variables in the resulting gdscript, everywhere they are referenced they will be replaced with their value at compile time.C style conditional compilation with @if/@else/@endif preprocessor statements. The expression after the preprocessor statement can be any constant Expression or a constexpr/consteval variable.
C++ style type aliases with
using CustomAliasName = Stringsyntax.C style preprocessor
@includestatements that can be used to paste the contents of another file directly where the@includeis found. The argument must be in double quotes and it must be a valid path to a file that FileAccess::open can open.C style preprocessor
@definestatements that define simple macros. Can also use@undefto undefine a macro that was previously defined.
Here is a snippet of some cool stuff you can do:
# AliasusingStringDict=Dictionary[String, String]
varanother_dict: StringDict= {"1": "22", "3": "2"}
constexprconstexpr_test: float=sqrt(2)
constexprconstexpr_test2: float=constexpr_test+sqrt(2)
@define TEST_MACROreally_long_variable_name_that_is_very_longvarTEST_MACRO: int=0
@define THINGvarthing1: int=0THING
@undef THINGvarTHING: int=0funcconstexpr_usage_test_func(x: int) ->int:
varvalue: int=constexpr_test2+xreturnvalueconstevalconsteval_test: float=pow(2, 12)
funcconsteval_usage_test_func(x: int) ->int:
varvalue: int=consteval_test+xreturnvalueusingUnion[String,int,float] =Arrayfunctest_union_alias() ->Union[String,int,float]:
returnArray()
usingOptional[String] =Variantfunctest_union_alias2() ->Optional[String]:
if1:
returnString()
else:
returnnull# Conditional compilation with constant expressions
@ifDEBUG_ENABLEDfuncsuper_secret_debug_function() ->void:
print("I like cats")
@elsefuncnormal_non_debug_function() ->void:
print("I like dogs")
@endif
constexprcond_comp_var: int=0constexprcond_comp_var_two: int=cond_comp_var+4constevalpower_level: int=sqrt(9001+sqrt(2) *pow(4, 9)) *5000
@ifpower_level>9000funcget_favorite_animal() ->String:
@ifDEBUG_ENABLEDreturn"Frogs are my favorite"
@elsereturn"Rabbits are cool"
@endif
@endif
constexprx: String="Hello"+" World "+"!"constexpry: Vector3=Vector3(5, 5, 5)
constexprz: int=999constevalTEST: Vector2=Vector2(0, 0)
@ifTEST==Vector2(1, 0)
functest_test_test():
print("hello")
@endifAfter running through the preprocessor the above code will get transformed into the following gdscript code:
funcglobal_function_test() ->void:
print("Hello World!") # Hi# Aliasvaranother_dict: Dictionary[String, String] = {"1": "22", "3": "2"}
constconstexpr_test: float=1.4142135623730951constconstexpr_test2: float=2.8284271247461903varreally_long_variable_name_that_is_very_long: int=0varthing1: int=0varTHING: int=0funcconstexpr_usage_test_func(x: int) ->int:
varvalue: int=constexpr_test2+xreturnvaluefuncconsteval_usage_test_func(x: int) ->int:
varvalue: int=4096.0+xreturnvaluefunctest_union_alias() ->Array:
returnArray()
functest_union_alias2() ->Variant:
if1:
returnString()
else:
returnnull# Conditional compilation with constant expressionsfuncnormal_non_debug_function() ->void:
print("I like dogs")
constcond_comp_var: int=0constcond_comp_var_two: int=4funcget_favorite_animal() ->String:
return"Rabbits are cool"constx: String="Hello World !"consty: Vector3=Vector3(5, 5, 5)
constz: int=999Still mostly experimental, there are likely some edge cases that can result in broken output. The preprocessor also doesn't report hardly any errors, it mostly just passes everything straight to gdscript. Please make an issue if you find any bugs.
Debuggablity also suffers a lot when compiling to gdscript. The line numbers do not line up perfectly in the compiled script due to things like conditional compilation. So when you get errors from the gdscript vm at runtime the line number they report might not be the same as the line number was before running the preprocessor. Usually the error gives enough information to figure out the bug but it might make it marginally harder to figure out where errors come from.
To run the tests run godot with godot --headless --script ./gdp_compiler.gd --quit. This will compile test.gdp to test.gd. You can then copy test.gd into a godot project and open the compiled script in the editor.
Right now there isn't a proper API, only a compile function that takes a input file and an output file as parameters.
I think the ideal way to do it in the future would be to make an EditorPlugin and have it compile automatically on save in the code editor so you can get instant feedback from gdscript's parser and LSP.
The syntax highlighting will be pretty bad in most editors if you use the default gdscript syntax.
If you use Sublime Text you can install SublimeGodot and use the gdp syntax, then if your files have the gdp extension you'll get correct highlighting for everything new.