Skip to content

CreatingBackend

ameharoo edited this page Aug 15, 2024 · 2 revisions

Implementing Your Own Backend

EN RU

Relevant as of version 1.0.0

πŸ’… This article will describe the algorithm for implementing your own backend for Mess, using "MyLangBackend" as an exampleβ€”a backend for "MyLang."

---
title: Relationships Between Backend Components
---
graph TD;
    backend.py:MyLangBackend-- Creating a Message ---> mylang.py{{Is message implemented in messages/mylang.py}}
    backend.py:MyLangBackend-- Rendering a Message ---> mylang.py
    mylang.py --> |Yes| messages/mylang.py
    mylang.py --> |No| messages/base.py
    messages/base.py-- Rendering a Message --> d(NotImplementedException)
    messages/mylang.py-- Mangling -->backend.py:MyLangBackend;
    messages/mylang.py-- Rendering a Message -->templates/mylang/*.j2;
    templates/mylang/*.j2-- Retrieving Message Information -->messages/mylang.py;
    
Loading

1. Implementing MyLangBackend - the Main Backend Class

Our backend class, inheriting from Backend, should be declared in backend.py below all previously declared classes. This is necessary for the correct functioning of the automatic search for implemented backends.

πŸ’ As of now (version 1.0.0), backend search occurs in mess.py. In future versions, this will be changed and moved to a separate file.

So, let's add this code to the end of the backend.py file:

class MyLangBackend(Backend):
    # Backend code
    name = "mylang"
    # Template directory name
    path_to_templates = "mylang/"

    def render_message(self, message:  messages_base.Message):
        return super().render_message(message)

Here, we create the MyLangBackend class, inheriting from Backend, and define the values for its default fields.

The name field defines the addressable name of the backend, which is used when selecting backends when the user runs the mess.py <name> <out> <in> generation script.

The path_to_templates field specifies the location of the backend's template files. This path is constructed as /templates/<path_to_templates>, so in this case: /templates/mylang/.

πŸ’ In this example, the MyLangBackend::render_message method is deliberately left in, although it could be omitted here. This was done to show that there is an opportunity to override the default algorithm instead (see the graph above).

πŸ™Œ A full description of the base class Backend can be found here or in the Backend.py file.

2. Implementing All Messages for MyLangBackend

The backend creates instances of the necessary messages, first built-in, then user-defined (from the ini file). For creation, it uses either the message implementation from messages/base.py or, if available, from messages/mylang.py.

πŸ’ This was done so that when a new message is added to base.py, and it does not yet have an implementation in the current backend, and it is used in the user's description, a NotImplementedException is thrown, allowing immediate identification of such a message.

In the /messages/ directory, create a mylang.py file where all messages from base.py are implemented. The main task here is to override the base.Message::render and base.Message::get_render_template methods, where the exception is thrown.

πŸ™Œ A list of all built-in messages registered in Mess can be found here or in the messages/base.py file.

Here is an example of such a message (taken from imhex.py):

class Message(base.Message):
    def __init__(self, name=None, fields=None, generic_args=None, docs=None):
        super().__init__(name, fields, generic_args, docs)

    def render(self, backend: 'Backend'):
        return backend.get_template(self.get_render_template()) \
                      .render(data={'message': self, 'backend': backend})
    
    def get_render_template(self) -> str:
        return "Type.j2"

Here, the mylang.Message::get_render_template method is overridden to simply return the template name, as well as the mylang.Message::render method.

In the mylang.Message::render method, the template is retrieved and rendered by passing data.

πŸ’ To implement your own backend, it is best to copy base.py and implement the messages there, or use cpp.py, which already includes template work.

There is great potential here to change input parameters, depending on what the template accepts.

3. Creating Templates for MyLangBackend

πŸ’ Here's where things get really interesting! This is where the rendered result's appearance is defined.

In the /templates/mylang/ directory, create a Type.j2 file with the following content (taken from templates/imhex/Type.j2):

{% set msg = message %}
{# First simple fields (non-variative), then complex, variative #}
{% set sorted_fields = msg.fields | sort(attribute="message.is_variative") %}

struct {{ msg | message_type }} {
    {% for field in sorted_fields %}
    {{ field | field_type }} {{ field | field_name }};
    {% endfor %}
};

πŸ™Œ The template is greatly simplified for better understanding (for example, it lacks support for comments, protocol hashing). Full, real examples can be found in /templates/.

This template implements a structure with a mangled message name using the message_type filter and the fields of this message.

πŸ™Œ A full list of filters can be found here or in the filters.py file.


--

In the end, I hope I have managed to at least roughly explain how to implement your own backend. This article and all others will be gradually updated to make Mess more understandable for everyone πŸ™†β€β™€οΈ

πŸ™‹ If you have any questions, feel free to ask them in the Discussions or Issues of the Mess repository on Github. Suggestions and PRs are also welcome!


Back to: Home | Implementing Your Own Backend