Aller au contenu

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() et write(), 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\", pour permettre une meilleure écriture et lisibilité du code.

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 :

  1. Le symbole de l'opérateur. Par exemple, "+".
  2. La priorité de l'opérateur. (cf. Opérateurs pré-existants)
  3. 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) (?).
  4. 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.
  5. 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.
  6. 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.

Retour en haut de la page