Defining a decoder

The goal of this guide is to bootstrap you into making your own color decoder using tools from thcolor. For clarity, you should first read Color expressions, especially the Decoders section.

For this guide, we will want to do the following:

  • Create our TutorialColorDecoder with extended hexadecimal color support.

  • Create a sum function which can take 2 to 5 numbers and sum them.

  • Create a diff function which takes 2 numbers and return the first one minus the second one.

  • Create a reverse_diff which takes 2 numbers and return the second one minus the first one.

  • Create an transparent function which returns the alpha value as a byte between 0 and 255, and returns the transparent color when used as a value.

  • Create a awesome-color constant which evaluates as blue.

Setting up the framework

First of, the elements you will want to import are the following:

For our exercise, we are going to use thcolor.colors.SRGBColor and no angles.

Our base code is the following:

from thcolor.decoders import MetaColorDecoder, alias, fallback
from thcolor.colors import Color, SRGBColor

__all__ = ['TutorialColorDecoder']

class TutorialColorDecoder(MetaColorDecoder):
    pass

As we want extended hexadecimal colors to be read as well, we need to define the corresponding option. As described in thcolor.decoders.ColorDecoder, the option for doing this is __extended_hex_support__, which gives the following class definition:

class TutorialColorDecoder(MetaColorDecoder):
    __extended_hex_support__ = True

Defining the sum function

As described in the constraints, the sum function will sum 2 to 5 numbers; the type of these numbers is not given, so we have three options:

  • If the hint on these numbers is int, only integers will be accepted by the function, and floats with decimal parts will throw an error.

  • If the hint on these numbers is float, integers will be converted to float before being passed on to the function.

  • If the hint on these numbers is typing.Union[int, float], the function will receive both depending on what the syntax was in the original expression.

Here, for simplicity, we choose to use float. The resulting code is the following:

class TutorialColorDecoder(MetaColorDecoder):
    # ...

    def sum(
        a: float, b: float, c: float = 0,
        d: float = 0, e: float = 0,
    ) -> float:
        return a + b + c + d + e

Defining the diff and reverse_diff functions

As described in the constraints, the diff function will compute the difference between two numbers. Based on what we’ve learned in the previous function, we can do the following:

class TutorialColorDecoder(MetaColorDecoder):
    # ...

    def diff(a: float, b: float) -> float:
        return a - b

Now for the reverse_diff function, we could define it independently, but in order to save time, we will use thcolor.decoders.alias. This function takes the following arguments:

  • The name of the function to alias.

  • The new order of the arguments, by name.

Based on this information, the thcolor.decoders.MetaColorDecoder will create the function out of the previous function (by calling it) and take all related annotations and default values (if possible and available).

For our current case, we can define our function the following way:

class TutorialColorDecoder(MetaColorDecoder):
    # ...

    reverse_diff = alias('diff', args=('b', 'a'))

Note that aliases can be defined anywhere within the class, even before the aliased function.

Defining the transparent function with a fallback value

For the transparent function, we will get the alpha value, multiply it by 255 and round it to the nearest integer. However, how do we add a fallback value for the function? thcolor.decoders.fallback comes to our rescue!

Once our function is defined, we use thcolor.decoders.fallback as a decorator by giving it the value the function should fall back on when used as a constant.

The resulting code is the following:

class TutorialColorDecoder(MetaColorDecoder):
    # ...

    @fallback(SRGBColor(0, 0, 0, 0.0))
    def transparent(color: Color) -> int:
        return int(round(color.alpha * 255))

Defining the awesome-color constant

For defining a constant, we can use affectations, as for aliases. But how do we define names with carets when they are illegal in Python names? We can replace it with _; the color decoders in thcolor are not only case-insensitive, they also treat carets and underscores the same.

We can thus do the following:

class TutorialColorDecoder(MetaColorDecoder):
    # ...

    awesome_color = SRGBColor.frombytes(0, 0, 255)

Testing the resulting class

Putting all of our efforts together, we should have the following code:

from thcolor.decoders import MetaColorDecoder, alias, fallback
from thcolor.colors import Color, SRGBColor

__all__ = ['TutorialColorDecoder']

class TutorialColorDecoder(MetaColorDecoder):
    __extended_hex_support__ = True

    def sum(
        a: float, b: float, c: float = 0,
        d: float = 0, e: float = 0,
    ) -> float:
        return a + b + c + d + e

    def diff(a: float, b: float) -> float:
        return a - b

    reverse_diff = alias('diff', args=('b', 'a'))

    @fallback(SRGBColor(0, 0, 0, 0.0))
    def transparent(color: Color) -> int:
        return int(round(color.alpha * 255))

    awesome_color = SRGBColor.frombytes(0, 0, 255)

Now that our class is defined, we can instanciate and test our decoder:

decoder = TutorialColorDecoder()
results = decoder.decode(
    'awesome-color '
    'sum(sum(1, 2, 3, 4), reverse_diff(transparent(transparent), 4))',
)

print(results)

And the result we obtain are the following:

(SRGBColor(red=0.0, green=0.0, blue=1.0, alpha=1.0), 14.0)