-
-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add literal quoted strings with embedded variables (f-strings) to GDScript #157
Comments
Now that I think about it, are Python's f-strings internationalization-friendly? If not, this may hinder their real-world use cases. In contrast, format strings can easily be localized as their placeholders are just text, not evaluated expressions. |
I've had use cases where I had to do
Keeping track of the indices to match where your |
For internationalization, we could have a |
another downside of the current implementation is, that there are many languages flipping the order of certain words / digits, likely when counting objects. Indexed expressions could also solve this issue! |
This is exactly what the |
Oh, didn't notice until now! Thanks! :-) |
I made same idea here #3403 Here is my syntax idea Using a symbol like var id = 123
var name = "Ahmed"
var age = 21
print("Hi I'm @name, my age is @age, id = @id") If we want to use expressions, we can do it like this for i in 10:
print("No. @(i + 1), @(i * i), @(i + i)")
# normal way:
print("No. " + str(i + 1) + ", " + str(i * i) + ", " + str(i + i)) in Dart can make a complex expression inside template strings like a ternary print("There are ${x < 10 ? "a few" : "many"} people in this room"); we can use the same way and put it in godot using template strings print("There are @('a few' if x < 10 ? else 'many') people in this room") |
@AhmedElTabarani I wouldn't enable string interpolation for every string, as this can cause difficult-to-diagnose bugs (on top of having a small performance overhead). Instead, string interpolation should be explicitly enabled by prefixing the string with a literal such as |
@Calinou I get it, do what you think is right and better in terms of complexity. |
You could probably also use the backtick (`) as well just like JavaScript. |
< tl;dr In Python, you'd just
From my experience, in GDScript out of the box you have the following options to format a string.
The call is like 3 times longer than it should be and forces you to a lot of repeating. Best in terms of conciseness:
Yes, this is concise, but look at what happens to readability. Then you have another option, that is not formatting but concatenating:
This is quite readable and not a pain to write, but I think it's unelegant and ugly. Since I was uncomfortable with all these options, I came up with a workaround to emulate Python's fstrings:
This way, all the variables in the script are mapped automatically and the formatted string is returned. I tried putting the function in an autoload to be reusable but, passing the node as an additional argument, for some reason all the variables are Null, so I'm forced to put the function in every script where I need it. Anyway, adding something like this in the String class (or in the Node class, in its current form) to be called at will, would be great. |
Good concept. You can get that to work as an autoload/singleton as below by passing self as a parameter. However there is still the problem with printing local variables. Unfortunately there is no way to get them in the f() method. Passing them also defeats the whole purpose of this formatter. If you can figure out to access locals vars in the f(), then this would be ideal as a plugin. Edit: I just realized there is an even simpler method that can be used. However the locals vars still an issu. #Format.gd autoload method
extends Node
func f_old(script, string : String ) -> String:
var map = {}
var this_script: GDScript = script.get_script()
for variable in this_script.get_script_property_list():
map[variable.name] = script.get(variable.name)
return string.format(map)
#Simpler method
func f(script, string : String ) -> String:
var map = inst2dict(script)
return string.format(map) #test calling script
var first_name = "John"
var last_name = "Smith"
var age = 31
var profession = "designer"
func _ready():
var salutation="Mr." # this will not work if passed to f()
print(Format.f(self,"{first_name} {last_name}, {age}, is a {profession}.")) |
Hey sorry @wyattbiker, I somehow missed your reply. func _ready():
var salutation = "Mr." # this will not work if passed to f()
print(Format.f("{salutation} {first_name} {last_name}, {age}, is a {profession}.", self, {"salutation" = salutation})) Format.gd autoload func f(string : String, script : Node, locals := {}) -> String:
var map = inst_to_dict(script)
for local in locals:
map[local] = locals[local]
return string.format(map) I moved the I'm gathering more info about accessing locals but I've found only this |
The only reason for me to use f-strings in Python is print(f"User: {id=} {name=}")
# Outputs:
User: id=42 name='Godot' |
Was hoping that this cause would already have people behind it. I agree that the current methods are pretty verbose and not very user-friendly. What about using C#'s method of using $?
Output: It's already available to people who are using C# to code projects in Godot. It would just be parity. |
|
Fair point. I hadn't considered that. I'm all for just getting it working in that way. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@P3NG00 Please don't bump issues without contributing significant new information. Use reactions on the first post instead. |
I feel like a lot of the discussion in this thread revolved around the idea that .format is better for internationalization, or else focused on what syntax would be best for f-strings themselves. I think this misses the key idea that f-strings are great for many typical use cases. Internationalization is important, but if I were a serious game shop I'd plan for that from the start. I don't think it makes sense to disregard the feature because it wouldn't be useful for the i18n crowd. Indeed, it's still useful for local debugging and development, even if the f-strings don't show up in production code. I use the python f'{var=}' syntax all the time in debugging and in writing good assert messages, for instance, and the existing Godot solutions are much more verbose. As for the best syntax, lots of good ideas there. Hard to beat the python syntax though, it's battle-tested and well-loved. |
Python Syntax, Javascript Syntax, a weird blend or something completely new, I don't care but some version of being able to use ANY kind of literal string formatting in gdscript soon is important to me. |
Found recently this library where it could fit to part the main usage FMT library print(f"Don't {}", "Panic")
formattedString = f"Don't {}"%["Panic"] with under the hood (cpp pseudo code) fmt::print("Don't {}"_fmt, "Panic");
formattedString = StringFormat("Don't {}"_fmt, "Panic"); FMT is also battle-tested solution in cpp and have the good taste :
You loose maybe the ability to choose the position of the argument you have in some syntax proposition, but I think it is more readable if you don't have this feature. |
Godot's own Switching |
Here's my 2c about the syntax: Python fstring syntax ( I much prefer the swift syntax though: "a = \(a)" This syntax introduces two fewer characters ( Anyway, I also got something more interesting to share: An outline for how this feature could work. Implementation ProposalI happen to work on a programming language myself, and found myself confronted with string interpolation. The solution I came up with involves a stateful lexer, but keeps the grammar itself contextless. The advantage of this is that this is much easier to implement that switching to a context grammar just for string interpolation. Basically, when a string is started, the lexer adds a virtual "String begin" token. Then, it enters 'string parsing' mode, emitting a string until either The compiler later gets an array of strings and expressions, and decides how to compile them. With the simplest implementation, it will just compile to a format string call. Example"A \(b) c \(d)\(e)" results in the tokens
The grammar interprets strings as such:
When a string was parsed, the compiler gets a list of tagged unions (pseudo-rust code): if elements.size() == 1 {
match elements[0] {
String(string) => return compile_string(string),
Expression(expression) => return compile_expression(expression),
}
}
let mut full_string = "";
let mut format_parts = vec![];
for item in elements {
match item {
String(string) => full_string.append(string),
Expression(expression) => {
full_string.append("{}");
format_parts.append(compile_expression(expression));
},
}
// [...] emit string format call like:
// format(<full_string>, *format_parts)
return compile_call(format_string, compile_string(full_string), *format_parts)
} The final compiled code is equivalent to: format("A {} c {}{}", b, d, e) This approach turned out very clean for me, and fairly powerful because you can interpret the string parts however you like. As opposed to Python f-strings' implementation, it can handle arbitrarily deep strings, such as You can peep my Lexer for this here: https://github.com/Ivorforce/Monoteny/blob/main/src/parser/lexer.rs Note that my implementation joins strings into string appends (and elements are function calls) rather than format strings, but it's the same idea. |
Describe the project you are working on:
Coding applications using Godot/GDScript
Describe the problem or limitation you are having in your project:
Would like to format literal quoted strings with embedded global/local variables and expressions by preceding the quoted string with the formatter 'f'. See example below.
Would like the option to use embedded f-string formatting similar to Python.
Keeps GDScript consistent with Python as much as possible but provides less wordy and error prone methods to display strings.
Describe how this feature / enhancement will help you overcome this problem or limitation:
Makes string formatting simpler and less error prone and uses less code to format strings.
Show a mock up screenshots/video or a flow diagram explaining how your proposal will work:
Example code single line literal string formatting.
Output>> The alphabet starts with abc and numerals start with 0
Example code using multiline string formatting with embedded expression.
Output>> '\n Hi Godot.\n You are a Game Engine.\n You are version 3.\n'
Describe implementation detail for your proposal (in code), if possible:
Implementation specs should follow Python 3 where possible:
https://www.python.org/dev/peps/pep-0498/
If this enhancement will not be used often, can it be worked around with a few lines of script?:
Will be used often. The alternative is using % or .format() formatting which is much more verbose.
Is there a reason why this should be core and not an add-on in the asset library?:
This enhancement needs to be part of the core along with the other formatting options since it is a syntax modification.
Bugsquad edit (keywords for easier searching): template literals, string interpolation
The text was updated successfully, but these errors were encountered: