Skip to main content
GameDev.net gamedev.net
🔒 Locked

Adding Support for Custom Literal Constants

Started by HenryAWE Dec 19, 2024 at 4:02 PM 31 replies 11.5k views
Original Post
HenryAWE
HenryAWE

My recent project needs fixed point float, it would be nice to have AngelScript support custom literal, e.g. 3.14f32 to define fixed32 literal constant. I've noticed that some people already suggested this feature in To-Do list a decade ago.

I'm investigating the compiler of AngelScript and trying to add something like user-defined literals of C++ (operator""_suffix).

My currently planned solution is to add new behaviors asBEHAVE_LITERAL_CONSTRUCT/FACTORY and use the name of behavior as the suffix, e.g. RegisterObjectBehaviour("void f32(double)") for f32 suffix.

The parameters of special functions can be one of the following (with example usage)

  1. uint64: void h(uint64) for 1h for convenient date time interface
  2. double: void i(double) for imaginary number.
  3. const string∈ for constructing a literal from string. It requires the host to register string support at first.
  4. int∈, uint, the C++ side will be const char*, asUINT. It will directly pass the source code of literal in script for parser provided by user. This might need the engine to expose some utilities such as tools for parsing script string literal.

For example, maybe we can implement a fmt suffix for formatting after this feature is done.

int val = 42;
string str = "hello";

string result = "val = {val}, str = {hello}"fmt;
// result is "val = 42, str = hello"

Please give some suggestion on this solution.

For reference: User-defined literals (since C++11) - cppreference.com

None
Miss
Miss

This sounds nice. I would also recommend instead of only supporting suffix, also support prefix. This is a bit more common in other programming languages when it comes to interpolated strings. For example, in C# you could do: $"val = {val}".

WitchLord
WitchLord

I don't really have any suggestion on this. I never started looking into this. I'm travelling for the next few weeks, so I won't be able to look into it this moment.

But I feel you're on the right path. To Miss' suggestion I think you could add to the function name something like _suffix or _prefix to tell the compiler which option to use.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
kammes
kammes

Your approach is correct. For flexibility, expose utility functions for parsing raw literals (e.g., int∈ as const char*). Support numeric (uint64, double) and string-based (const string∈) literals for varied use cases.

Here’s a more detailed example of how your solution might look with the f32 suffix:

// registering behavior for f32 suffix
engine->RegisterObjectBehaviour("Fixed32", asBEHAVE_LITERAL_CONSTRUCT, "void f32(double)", asFUNCTION(Fixed32Constructor), asCALL_CDECL);

// example script usage
Fixed32 fixedValue = 3.14f32;

For the fmt literal:

// registering fmt behavior
engine->RegisterObjectBehaviour("string", asBEHAVE_LITERAL_CONSTRUCT, "void fmt(const string∈)", asFUNCTION(FormatConstructor), asCALL_CDECL);

// example script usage
int val = 42;
string str = "hello";
string result = "val = {val}, str = {str}"fmt;

The FormatConstructor would parse the string, replace placeholders with variable values, and return the resulting string.

-kammes- tag unblocked
HenryAWE
HenryAWE

Maybe we can have asBEHAVE_LITERAL_CONSTRUCT/FACTORY_PREFIX/SUFFIX for both user-defined prefix / suffix literals. But the user-defined prefix should only accept string, because f123 has ambiguity with an identifier.

In addition, maybe it also needs something like template callback to validate the user-defined literal? For example, if you have a suffix for fixed point number registered as void f32(int∈, uint) with a custom parser, you may want to only accept 3.14f32, while rejecting "str"f32 at compile-time. So, I think maybe there should be new behaviors called asBEHAVE_LITERAL_CALLBACK_PREFIX/SUFFIX too. The parameters are similar to the user-defined literals, but instead returning a Boolean value for validation.

None
WitchLord
WitchLord

I think it might be more elegant to do a solution similar to asBEHAVE_LIST_FACTORY https://angelcode.com/angelscript/sdk/docs/manual/doc_reg_basicref.html#doc_reg_basicref_4

Example “void f(double) {'f32' suffix}”

This will make it easier to support symbols such as $ that are normally not part of identifiers.

Yes, support for a callback is necessary as well to catch errors at compile time.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
HenryAWE
HenryAWE

Finally I have sometime to continue this. I think the approach similar to declaring a list factory would be a better plan.

Another problem will be how to deal with conflicting literal names. For example, if a user register a void f(double) {'f' suffix}, it may conflict with the built-in suffix for a float literal.

None
JudeHunter
JudeHunter

Thank you so much for the help.

HenryAWE
HenryAWE

I've done the parsing function declaration part. Registering a user-defined literal will look like this

@witchlord But I have difficulty in finding where to modify the compiler. It seems that the asCCompiler::CompileExpressionValue is responsible for compiling constants. Is it correct to add code for calling user defined literal constructor/factory here?

None
WitchLord
WitchLord

Yes, that would be the correct spot. It should be done similar to how the string constants are done.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
WitchLord
WitchLord

keeping this thread alive 🙂

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
HenryAWE
HenryAWE

@WitchLord

WitchLord said:

keeping this thread alive 🙂

I'm quite busy recently, because I'm about to graduate from the university. There are so many things to do. I'll come back to continue on this thread 2 ~ 3 weeks later.

None
WitchLord
WitchLord

No worries. Take you time. I just bumped up the thread because GameDev.net locks the threads after a few weeks of no activities.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Paril
Paril

Hey Henry,

I'm interested in helping on this feature as I currently also require something similar. I end up constructing constants of a particular primitive type every frame for things like time, and having them compiled as constants like strings would eliminate the issue. How far have you gotten in the AS side of things? Would you be willing to share the patch so far and I can try to pick up what's remaining to be implemented?

HenryAWE
HenryAWE

Paril said:

Hey Henry,

I'm interested in helping on this feature as I currently also require something similar. I end up constructing constants of a particular primitive type every frame for things like time, and having them compiled as constants like strings would eliminate the issue. How far have you gotten in the AS side of things? Would you be willing to share the patch so far and I can try to pick up what's remaining to be implemented?

I finally have some time. Previously, I used svn to clone the AngelScript library, and then send diff files via email. But I'm not familiar with svn, so I consider to put the existing code in a git branch (forked from the mirror library on GitHub). I will update the link in this thread when it is done.

None
Paril
Paril

Yeah that works. I can help test when it's ready. Thanks!

Paril
Paril

I had a thought about this: I wonder if instead of starting with the actual literal operator, this feature should start as a generic “pure” modifier which can be added to host functions. For example:

float sin(float) pure

This would mark the function pure; not modifying any globals, always providing the same output for the same inputs, etc. A pure function could, at compile time, be inlined into a constant if the function is called with inputs that are either constants or literals.

This would happen from the inside-out (not sure what the term is in AST-esque terms but what I mean is if you have nested function calls, it would evaluate pure function calls on the deepest levels first, so that it can properly constant-ize coming back out). For instance:

const float pi; // provided by host, not set in this example
float x = sin(pi); // at compile time, this function call can safely be inlined as `float x = 0`
float y = sin(0); // same as above
float z = sin(sin(1)); // this would inline the inner sin first, and then the outer sin, so it would become `float z = 0.7456...;`

Then, this feature can easily be used to handle the literal feature since those are just pure function calls written a different way (eg, gtime_t t = time_ms(0) is identical to gtime_t t = 0_ms because 0_ms is just rewritten at compile time into time_ms(0), which is then inlined).

The only thing that I have a hard time with is how this could apply to constructors. Like, reference types not being allowed to be pure at all is probably OK, although I guess in practice it could be done by calling the factory during compilation, calling the pure function, then destroying the object. Custom value types, though, would need some way of having pure constructors which can be used to pass them (either by value or by ref) to pure functions. eg:

vec3(float, float, float) pure

which allows this simple constructor to be passed to something theoretical like:

float sum(vec3 v) pure or float sum(const vec3 ∈ v) pure

as:

float x = sum(vec3(1, 2, 3))

or:

const vec3 t(1, 2, 3);
float x = sum(t);
WitchLord
WitchLord

pure functions is definitely an interesting feature that may be incorporated, it is no my to-do list already. But it is a different feature from the custom literals. Yes, the two might be combined, but there is no need to implement pure functions in order to provide the support for custom literals.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
WitchLord
WitchLord

Reopening this thread since there is some movement on the development again.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
HenryAWE
HenryAWE

I have some difficulties implementing the script parsing part. Taking the string constant as an example, I'm modifying the asCParser::ParseStringConstant for parsing script literal like "hello"_suffix , but it seems that support prefix literal is not feasible in this way. Maybe should we just support suffix literal at first?

None

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.