-
Notifications
You must be signed in to change notification settings - Fork 72
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
Tag-based OO-like code #234
Comments
I really like this proposal! I've seen many "OO" attempts in Pawn and this seems like the most elegant. This reminds me of how Golang and Python handle things (Golang being my preferred option, Python's This was actually along the lines of something I wanted to introduce using the pawn-parser. My only concern is that I'm not sure how I or others feel about introducing rather large changes (even though they are purely additive) to the compiler. Something I was planning to introduce to sampctl was the idea of "plugins" which would basically act similarly to webpack style plugins that "transpile" javascript - a common practice in the JS world is to build language syntax extensions then allow them to be transpiled back to early versions (such as ES5) using build toolchain plugins. The idea with sampctl was to introduce a pre and post build commands that could be anything from search-and-replace apps to fully featured language transpilation which would facilitate things like this and ideas discussed here: Southclaws/ScavengeSurvive#441 |
I just realised that this is basically https://en.m.wikipedia.org/wiki/Uniform_Function_Call_Syntax |
#pragma rational Float
#define this. THIS<Object>
#define THIS<%3>%0(%1) %3_%0(%3:THIS_:this,%1)
#define THIS_:this,) this)
native Object_SetPos(Object:this, Float:x, Float:y, Float:z) = SetObjectPos;
native Object:Object_Create(modelid, Float:x, Float:y, Float:z, Float:rx = 0.0, Float:ry = 0.0, Float:rz = 0.0, Float:drawDistance = 0.0) = CreateObject;
main()
{
new Object:this = Object_Create(1337, 0.0, 0.0, 4.0);
this.SetPos(5.5, 6.6, 7.7);
} |
Is this really necessary? We already have problems with a 31-character name limit when hooking callbacks with long names; with member variables/functions this might become even worse. I think it shouldn't be very hard to make the compiler treat
This would solve the potential problem with the name length limit, and might open up a better way for function hooking. Also, the mechanism described above can be reused later for struct-like enumerations (#609), so we'll need it anyway. @Y-Less @YashasSamaga Would it be OK if I start implementing tag-based functions in the way described above? Any objections/suggestions? |
I think this should be an opt-in experimental feature controlled by a compiler flag for now. Maybe a few select libraries could provide an optional conditionally compiled OO-like code and provide feedback.
How would native functions work given that they have a tight identifier length limit? One way would be to not allow method overloads to be native functions. Instead, the user will have to use a normal identifier and then create a proxy to invoke that native.
This would completely decouple native functions from the tag based OO system.
I think another disadvantage of a simple lightweight mapping is error handling. It might be easier to have nicer diagnostics for OO code instead of some direct mapping. For example, "could not find method 'Method' for tag 'SomeTag'" instead of "undefined symbol 'SomeTag_Method'". There is also an option of mangling the name for internal use like it's done for user defined operators. If we restrict methods to be non-native non-public functions, we can store the function name as "Tag.Method". The length limit might cause a problem though. I always wondered if we could increase the identifier length for local functions (non-native & non-public). The mangled name will allow a version of
I think it's easier if we use mangled names with dots and extend the identifier limit for local identifiers (in a separate PR). The name limit extension can be kept for internal use or be made external. We can have a new flag to mark methods from normal functions (or simply [ab]use the presence of a dot in the managed name). It has been a really long time since I have worked with the compiler or the language itself. I am out of touch and cannot reason clearly about pawn or the compiler atm. Apologies for the same. I hope my comments make sense. |
These are some very good ideas, but regardless of the implementation there's still one major issue with this idea - pre-processor function hooking just doesn't work. The defines would be based on the canonical name (which may or may not contain Player.MySetPos()
{
}
#define SetPos MySetPos
playerid.SetPos(); // Great.
vehicleid.SetPos(); // Incorrectly replaced.
There's direct support for this in the compiler with native renaming already: native Tag.Function() = NormalNativeFunctionName;
That's something that's been considered a few times, and would be useful. Even natives can have longer names with the internal/external names.
Not really. If the "correct" way is better and not hard, there's no other reason not to do it.
I have no objections. I think there are still some issues to solve, but a prototype might help with that. |
If it all boils down to the preprocessor, then maybe we could implement a new hooking mechanism as a language feature? Doing this would kill two birds with one stone: solve the problem with hooks being preprocessor-dependent and have an easy-to-use hooking method, without the need to write all of that |
Well maybe, but I don't think that really addresses the root issue, that's just a patch for one symptom. |
I think I've solved the pre-processor issue: this.Func() Becomes: __tagof(this)Func(this) And from there normal pre-processor rules take over: TagFunc(this) And you can redefine this however you like. |
We just need to be careful not to parse This is even more complex with chaining: this.GetAngle().ToInt(); Becomes: Float_ToInt(Entity_GetAngle(this)); |
Making PAWN more OO-like has been discussed over and over and over again. While I'd personally be against a fully-fledged rewrite a-la SourcePawn Transitional Syntax,, I think I have a relatively light-weight proposal to introduce a good set of OO features very very little cost in terms of changes (and nothing breaking). Basically, just map this:
To this
This follows long-standing SA:MP
Module_Function
conventions, plus athis
convention, and would not introduce new requirements for passing around "objects", asthis
is still just a cell - what you want to do with that in terms of treating it as a handle to more data is entirely up to the library author (or whoever). It would even remain compatible with old code as callingTag_Function(handle, 0, 0)
would call the correct function even if it was declared with the newTag.Function()
syntax.Some libraries already use a macro to support
Library::Method()
syntax, which is why I didn't suggest adopting::
for this (plus it would probably be more complex to get working around the existing tag syntax). That could even be an avenue for writing global libraries:Natives would work the same:
Would compile as:
Clearly entirely compatible with the existing native.
To call the methods, use a strongly tagged variable (I'd suggest against allowing this on weak tags - far too likely to cause unexpected calls):
Compiles as (sort of):
Or more accurately:
Obviously that would require the tag of a variable to be known at compile time, and this could not work for run-time polymorphism:
Tag returns (and hopefully chaining) could work, but I know that would be much harder to implement as it would require more manipulation of the AST:
Or no reason to keep the same tag:
This is not full objects, and is mostly based around tag-based handles, but I think it goes a long way. I wrote something about supporting operators as well, but they already have overloading and wouldn't need the dot-call syntax.
The text was updated successfully, but these errors were encountered: