Ajouter un type
Les types sont la base du fonctionnement des scripts. Ce sont simplement des classes "encapsulées" dans une classe particulière : le ScriptType
. C'est le fait d'hériter cette classe et d'implémenter certaines interfaces qui permet de définir un comportement à l'objet que l'ont veut intégrer à Sqript.
Types¶
Pour créer un nouveau type, commencer par créer une nouvelle classe héritant de la classe ScriptType<T>
avec T
la classe de l'objet à implémenter. Par exemple, il est possible de définir un ScriptType<EntityPlayer>
, qui définira comment se comporte les instances de la classe EntityPlayer
dans un script. Ensuite, implémenter les méthodes abstraites de la classe mère.
La méthode la plus importante de la classe est le constructeur, qui va permettre d'encapsuler l'instance de la classe que vous souhaitez intégrer dans un ScriptType
. Un appel au constructeur super
suffit pour encapsuler la variable. Par exemple pour la classe EntityPlayer
:
public class TypePlayer extends ScriptType<EntityPlayer> {
public TypePlayer(EntityPlayer player){
super(player);
}
@Override
public ScriptElement<?> parse(String typeName) {
return null;
}
}
Compléter la méthode parse()
n'est pas obligatoire, mais peut être très utile dans certaines situations. Retourner null
désactivera la capacité du type à être parsé.
Jusqu'alors, le type n'est pas encore utilisable : il faut l'enregistrer. Pour ce faire, il suffit d'annoter la classe avec l'annotation @Type
:
@Type(name = "player",
parsableAs = {})
public class TypePlayer extends ScriptType<EntityPlayer> {
public TypePlayer(EntityPlayer player){
super(player);
}
@Override
public ScriptElement<?> parse(String typeName) {
return null;
}
}
Tip
Il est possible de surcharger la méthode toString()
pour définir le comportement de l'objet lorsqu'il sera converti en TypeString. Pour tous les autres types, il faudra passer par la méthode parse()
.
Tip
Il peut être intéressant de faire votre propre implémentation de la fonction hashCode() pour assurer un bonne gestion de votre type dans les tableaux ou les dictionnaires.
Vous aurez accès à l'objet encapsulé en grâce au paramètre object
de l'instance de votre classe.
Il est possible de complexifier le comportement du type en implémentant certaines interfaces :
ISerialisable
permet de définir le type comme étant enregistrable. Toute variable globale ayant un type n'implémentant pas cette interface ne pourra être enregistrée. Cette interface comprend deux méthodes,read()
etwrite()
, qui agissent sur des tags NBT. Elles sont respectivement appelées d'abord au lancement de Sqript pour charger la valeur de la variable, puis à la fermeture pour l'enregistrer.
Types primitifs¶
Les primitifs sont des types pouvant être instanciés sans passer par une expression. Le principe de création est le même que celui des types normaux, la classe à hériter devient PrimitiveType<T>
, et l'annotation devient @Primitive
. Un nouveau paramètre accompagne cette annotation, il s'agit du pattern de la primitive. Attention, ce pattern doit être une expression régulière.
De la même manière que pour les types normaux, il est nécessaire d'implémenter les méthodes abstraites de la classe mère. La méthode fromString()
est la méthode la plus importante des types primitifs, ainsi que leur constructeur, acceptant un argument du type encapsulé. De plus, une type primitif doit avoir un constructeur acceptant une unique chaîne de caractère qui sera à parser.
Voici par exemple le contenu du type primitif TypeString :
@Primitive(name = "string",
parsableAs = {},
pattern = "(?:\"((?:[^\"\\\\\\\\]|\\\\.)*)\")";
)
public class TypeString extends PrimitiveType<String> implements IOperable {
@Nullable
@Override
public ScriptElement parse(String typeName) {
return null;
}
public TypeString(String parameter){
super(parameter);
}
}
Le pattern est une RegEx, qui sont bien plus complexes que les *patterns, mais aussi plus flexibles et plus puissantes. Il est indispensable de les maîtriser afin de créer des types primitifs. Le paramètre match
passé au constructeur est le premier groupe capturé par l'expression régulière du pattern. Ici, le groupe capture automatiquement tout ce qui se trouve entre les guillemets, aucune manipulation n'est donc nécessaire. Cependant, dans le cas des nombres par exemple, il faut utiliser les méthodes adéquates.
Les mêmes interfaces que pour les types normaux peuvent s'appliquer aux primitives.
Tip
Par convention, on appellera également les classes de types primitives "Type\
Opérations & opérateurs¶
Établir une opération entre deux éléments n'est pas une tâche difficile, mais nécessite quelques bases en ce qui concerne les termes que l'on retrouve dans une expression mathématique. On appellera opérateur tous les symboles permettant de mettre en relation les opérandes. Les opérandes seront d'autres éléments provenant d'autres expressions. La notion d'opérateur dans Sqript est très abstraite, et il faut prendre en charge chaque cas d'opérateur pour définir correctement un type que l'ont désire pouvoir comparer, composer, etc. L'égalité =
notamment est un opérateur au même titre que l'addition +
, mais doit retourner un TypeBoolean vrai ou faux.
Enregistrer un nouvel opérateur¶
Il est possible d'enregistrer des opérateurs personnalisés lors du chargement de votre addon. Il suffira d'appeler la méthode registerOperator(ScriptOperator)
de la classe ScriptManager
pour enregistrer un nouvel opérateur. Un opérateur s'instancie via le constructeur de la classe ScriptOperator
, et nécessite au plus 7 arguments que sont :
- Le symbole de l'opérateur. Par exemple, "+".
- La priorité de l'opérateur. (cf. Opérateurs pré-existants)
- L'associativité de l'opération. Elle peut être de gauche à droite (
Associativity.LEFT_TO_RIGHT
), de droite à gauche (Associativity.RIGHT_TO_LEFT
) ou nulle (Associativity.NONE
) (?). - La binarité de l'opérateur. Un opérateur binaire compose 2 éléments pour en donner un nouveau, tandis qu'un opérateur unaire transforme 1 seul élément en un nouveau. Configurer à
false
si l'opérateur est binaire,true
s'il est unaire. - La manière dont l'opérateur doit être lu. Si la valeur est
true
, l'opérateur ne sera reconnu que s'il est séparé par au moins un espace de l'opérande de gauche et de l'opérande de droite. true
si l'opérateur est post-fixé. Ce qui est le cas du symbole factorielle!
par exemple, cela signifie qu'il s'écrit en notation infixée (la notation naturelle) après le ou les opérande(s) sur lesquels il s'applique.
Opérateurs pré-existants¶
Symbole | Description | Priorité | Associativité | Binarité | Mot | Position |
---|---|---|---|---|---|---|
+ | "+" Unaire | 14 | Droite à gauche | Unaire | Non | Pré-fixée |
- | "-" Unaire | 14 | Droite à gauche | Unaire | Non | Pré-fixée |
! | Factorielle | 14 | Droite à gauche | Unaire | Non | Post-fixée |
* | Multiplication | 12 | Gauche à droite | Binaire | Non | Infixée |
/ | Division | 12 | Gauche à droite | Binaire | Non | Infixée |
+ | Addition | 11 | Gauche à droite | Binaire | Non | Infixée |
- | Soustraction | 11 | Gauche à droite | Binaire | Non | Infixée |
>= | Supérieur ou égal à | 9 | Gauche à droite | Binaire | Non | Infixée |
<= | Inférieur ou égal à | 9 | Gauche à droite | Binaire | Non | Infixée |
> | Supérieur à | 9 | Gauche à droite | Binaire | Non | Infixée |
< | Inférieur à | 9 | Gauche à droite | Binaire | Non | Infixée |
= | Égalité | 8 | Gauche à droite | Binaire | Non | Infixée |
not | Négation logique | 5 | Gauche à droite | Unaire | Oui | Pré-fixée |
and | "Et" logique | 4 | Gauche à droite | Binaire | Oui | Infixée |
or | "Ou" logique | 3 | Gauche à droite | Binaire | Oui | Post-fixée |
Définir une opération pour un type¶
Les opérations entre les types sont toutes enregistrées dans la même Map, ce qui permet de les modifier si besoin. Pour enregistrer une opération, il suffit d'appeler la méthode ScriptManager.registerBinaryOperation()
(dans un bloc static de la classe du type par exemple) pour les opérations binaires et ScriptManager.registerUnaryOperation()
pour les opérations unaires. Il faut ensuite renseigner l'opérateur, le type de la classe à gauche, et le type de la classe à droite si l'opération est binaire. Par exemple, pour définir une addition entre deux nombres :
static{
ScriptManager.registerBinaryOperation(ScriptOperator.ADD, TypeNumber.class, TypeNumber.class,
(a,b) -> new TypeNumber( ((TypeNumber) a).object + ((TypeNumber) b).object) );
}
Vous pouvez utiliser le type ScriptType.class
pour indiquer que vous pouvez utiliser l'opération avec n'importe quel type. Cependant, cette définition sera considérée en dernière si d'autres définitions avec le même opérateur sont déjà enregistrées. C'est notamment le cas avec la concaténation de chaînes de caractères :
static {
ScriptManager.registerBinaryOperation(ScriptOperator.ADD,TypeString.class,ScriptType.class,
(a,b)-> new TypeString( ((TypeString)a).object + b.toString()) );
}
Et voilà, l'opération est enregistrée. Le compilateur d'expressions mathématiques de Sqript se chargera d'appliquer les opérations sur votre type en utilisant l'opération que vous avez défini.