Skip to content

Conversation

KoloInDaCrib
Copy link

@KoloInDaCrib KoloInDaCrib commented Jul 23, 2025

This PR adds the ability to merge two or more scripts into one using the _merge method. Merging works similar to other merging methods, just with a different way things are patched in the base class.
This method is only available with the hscript library installed.

Setup

In your project's ParseRules, you can set files and file extensions to have the SCRIPT as the file extension type. This will make it so that those specific files and files with the extension will be registered as scripts by Polymod and use the script merging method. For this method, only merging is available, appending will throw a warning.

The process behind this

How merging works is that both the base text and the merged text will be split into tiny blocks using hscript.Parser. If the parser throws an error, the merging process is aborted. After the blocks have been merged, PolymodPrinterEx prints out the new text to be used for the script and furhter merging processes (albeit not very prettily).
Different "blocks" use different methods of being inserted into the base script.

Packages

Packages are handled in a simple manner. If the base script doesn't have a set package, the package from the merged script will be used. Otherwise, nothing will happen.

Imports

All imports from the merged script are added onto the base script because duplicate imports are handled later in PolymodInterpEx.

Classes

The classes are the most complex part of the merging.
To start off, if the base script does not have a class named exactly like one from the merged script, the one from the merged script is added to the base script. If it does, you can use the newly-introduced metadata @:mergeOverride on the class to override the entire class from the base script.
Without the metadata, all fields are added from the merged class into the base class. If a merged field has the same name as a base class field, you can use @:mergeOverride on the field to override its value. Works on both variables and functions. Without the metadata, the merged field is ignored.
For functions, there exists another special metadata, @:mergeInsert(index). If a merged function has this, the contents of the merged function will be merged into the base function at the index index (an integer value, will default to 0 if not defined). This means that if you have a base function

function foo()
{
 trace("foo!");
}

and a merge function

@:mergeInsert(1)
function foo()
{
 trace("foo2!");
}

the final result will look (roughly) like this

function foo()
{
 trace("foo!");
 trace("foo2!");
}

Typedefs

Typedefs work similarly to classes, just much simpler. If there isn't a typedef with the same name as the merged typedef in the base script, the merged typedef is added into the base script.
Otherwise you can use the @:mergeOverride metadata to override the typedef.
If the merged and base typedefs have the same name and the typedefs are an anonymous structure, the missing fields from the merged typedef are copied over while the others are untouched.

Enums (EXPERIMENTAL BRANCH ONLY)

Enums don't support metadata, so their way of merging is rather simple.
If merged and base enums have the same name, the fields from the merged enum are copied onto the base enum.
Otherwise, the merged enum is added into the base script.

Closing

This will require a fuckton of testing so feel free to try it out and lemme know if there are bugs with anything! :]

this repo kicks ass and i can make PRs while browsing
hahahahaha dude this PR is so fucking funny it makes me wanna merge scripts without looking

@EliteMasterEric
Copy link
Collaborator

Without the metadata, all fields are added from the merged class into the base class. If a merged field has the same name as a base class field, you can use @:mergeOverride on the field to override its value. Works on both variables and functions. Without the metadata, the merged field is ignored.
For functions, there exists another special metadata, @:mergeInsert(index). If a merged function has this, the contents of the merged function will be merged into the base function at the index index (an integer value, will default to 0 if not defined). This means that if you have a base function

I like this solution, it's interesting.

It reminds me of Harmony's patches used in C# for Unity modding, and the SpongePowered mixins used for advanced Minecraft modding.

My thought is that the structure of these could be useful for developing this highly desirable feature going forward.

Of note, I would say that the annotations on fields and types in _merge should probably be mandatory, making it explicit whether the intent of each type is for them to replace or merge (and if merging, how that should be done).

also fix implements since i forgot to lol!
@KoloInDaCrib
Copy link
Author

I've made the requested changes - now field require the @:mergeAdd metadata to get added to the class. Otherwise the script will complain.
Though this change required to remove merging into Enums, as enum fields do not support metadata. And to be consistent I've also removed the ability to merge into anonymous structure Typedefs. Hope this is acceptable.

Copy link

@TechnikTil TechnikTil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this... is beautiful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants