-
-
Notifications
You must be signed in to change notification settings - Fork 404
Type Properties/Attributes #8165
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
Draft
sovdeeth
wants to merge
25
commits into
SkriptLang:dev/feature
Choose a base branch
from
sovdeeth:feature/type-properties
base: dev/feature
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Type Properties/Attributes #8165
sovdeeth
wants to merge
25
commits into
SkriptLang:dev/feature
from
sovdeeth:feature/type-properties
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
I also acknowledge how bloated this may make the already bloated __Classes classes, so I intend a future PR to go through and separate all the classinfos each into their own classes: |
…s, start parse-time checks for Contains
- allows stateful property handlers - removes use of AnyNamed - removes NameHandler in favor of just using ExpressionPropertyHandler - Avoid exposing silly cast to users when possible in PropertyBaseExpression - fix issue with handling DELETE and RESET changers in PropertyBaseExpression - add support for default toString impl in PropertyBaseExpression
cool request |
first pass breaking change, removes `size of itemstack`.
…x, add condition base class
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Problem
One of the most irritating problems that has plagued the Skript ecosystem for ages is the inability for two implementations of the same syntax pattern to exist at the same time. In Skript itself, it forces long, complicated Expr classes that handle 7 different types, and in addons it causes syntax conflicts or awkward naming schemes to avoid any possible conflict with other addons or Skript.
AnyX was somewhat recently introduced to assist in this case, by allowing any type to be converted to a uniform interface class that has a common method to use. This works well, but has little to no parse-time information capabilities. There's no way to check if a type that implements
AnyContains
can contain strings, items, or entities until runtime, when it's basically too late.It's also difficult to handle changers and any non-
get()
behavior with AnyX, due to the same reasons of limited parse time information. This can make determiningallowChange
and possible return types near impossible to do accurately.Solution
This PR introduces the concept of Type Properties (name wip, property is already rather prevalent in code). These are very free-form properties that can be registered with Skript using a name and a handler interface. Any ClassInfo can use the
.property()
method to declare that they implement this property and how they implement it.This means that Skript, SkBee, and Disky may all have their own implementations of
size of x
for their own types without conflicting. You could dolengths of ("xyz" and {skript-particle-rectangle})
with no issue.toggle x
could work for any type from any addon that wants to use that property.Implementing a Property
This system revolves around the
Property
class, which is, by itself, just a string and an addon reference. Any addon can register a property, though no two addons can register the same property name. Properties require a "handler" class to be supplied at creation to declare the interface all implementations must follow. This can be as simple as:or something much more complex, with changers or complicated behaviors. There are no restrictions, except for the handler must extend
PropertyHandler<Type>
, which is an empty interface that just says "This is a property handler".When a
ClassInfo
wants to implement a property, they use theClassInfo#property(Property, Handler)
method. This adds the provided handler implementation to the class' properties for later use. Each property may only be implemented once on aClassInfo
.Alternatively, something as simple as
length of
could use the existingExpressionPropertyHandler
handler, which provides basic methods for expression-like properties, such as conversion, acceptChange/change for changer support, and custom return types:Or for changer support:
A similar handler exists for simple conditions, the
ConditionPropertyHandler<Type>
.Creating a syntax that uses a property's handler
Up till now, this has been very simple for the user. The more complex part is creating a syntax that takes advantage of these properties to actually do something. For simple property expressions (properties that lead to patterns similar to the actual SPE syntaxes, like
name of x
), I provide a relative simple base class to extend:This does require the user to either use the
ExpressionPropertyHandler
hander directly, or have their handler implement it:In return, changers and property checks are handled for you. This is what I believe most properties will use.
Some handlers may require state, for which
newInstance()
andinit(Expression, ParserInstance)
should be overridden:Similarly, I provide an easily extensible base class for simple conditions:
Note this only works for simple conditions that do not require secondary input.
Custom property syntax implementations
However, this API is very powerful and allows way more outside of that. The power, though, comes at the cost of complexity. Users who want unique properties will have to handle property management, type checks, and evaluation themselves. It's not that bad but it is certainly complex.
To start with, users making custom syntax should consider inheriting
PropertyBaseSyntax
for standardization reasons. It provides agetProperty
andgetPropertyName
methods which can be used to determine property and for toStrings regardless of the details of the specific syntax.The main things to handle are:
Determining the relevant properties:
Nearly every property will contain something like this in their init():
These
PropertyBaseSyntax
methods are meant to assist in, respectively, converting an input expression into one that returns things with the given property and getting all the possible handlers that one could need given the expression's return types.getPossiblePropertyInfos
in specific is important, since it returns the most useful part of the api, thePropertyMap
.A
PropertyMap
is aHashMap<Class, PropertyInfo>
with 2 special methods:getHandler(Class)
andget(Class)
.getHandler
is the main method you should be using, which returns an appropriate Handler implementation for the given class.get(Class)
returns thePropertyInfo
for the given class or, if not found, determines the closest class to the given class that has a property in the map already. This lookup is cached, so after the call, a new entry should exist in the map, either to null if no property exists, or to aPropertyInfo
. This allows, for example,OfflinePlayer
to have a property implemented and forPlayer
to also use that property.Using the properties
For example, here's my implementation of
CondContains
using properties:Implementation details can vary wildly between properties, but
PropertyBaseExpression
is a good example of some of the more complex details a property may encounter.PropCondContains
is a good example of a simpler custom property implementation.Documentation
Any syntax element using properties should have the
@RelatedProperty("property name")
annotation added for documentation purposes. Each property requires a short description upon creation, describing what it is meant to represent. Likewise, each implementation on a class info also requires a short description, which should be used to describe exactly what on the type it represents, as well as any relevant info like available changers.Documentation is supported via the JSON docs. An array containing all properties with their related types and syntaxes is provided, and any syntax annotated as @RelatedProperty will have a list of all the classes that implement that property, as well as the info about the property itself. ClassInfos will have their properties listed out with the property id, name, and a class-specific description of how it implements the property, plus a list of related syntax ids.
Testing Completed
Only manual testing has been done so far.
Supporting Information
Completes: none
Related: #7644 #7919 #7675 #7416 #8136