Introduction to TagScript

Anatomy of a TagScript Block

In scripts, TagScript blocks are always written surrounded by curly braces ({ and }). A TagScript block consists of three sections:

{declaration(parameter):payload}

The declaration is mandatory for all blocks and communicates which block should be used to process a given segment of TagScript. Some blocks may only consist of a declaration.

The parameter can be mandatory OR optional, depending on the block being used. If present, it is always enclosed in parentheses (( and )) and immediately follows the declaration.

Some blocks require some sort of parameter to be passed, others can work with a parameter being passed but don’t need it. For those blocks with optional parameters, behaviour will often differ between their no-parameter and parameter-passed forms.

Whether a block requires a parameter or not is noted in the specific block’s documentation.

The payload can be mandatory OR optional, depending on the block being used (just like the parameter). If present, it is always preceded by a colon (:). The colon either comes immediately after the declaration (if no parameter is used) or immediately after the closing parenthesis of the parameter.

Whether a block requires a payload or not is noted in the specific block’s documentation.

If any of the sections violate their positioning rules in any way, the entire segment is rejected and returned as-is, without further interpretation happening.

Valid Block Structures

Given the above mentioned combinations, these are the only possible valid block structures:

{declaration}
{declaration(parameter)}
{declaration:payload}
{declaration(parameter):payload}

Examples for each combination:

  1. {declaration}SilenceBlock

  2. {declaration(parameter)}RedirectBlock

  3. {declaration:payload}CaseBlock

  4. {declaration(parameter):payload}IfBlock

Click on their names to see their documentation and some examples.

Nesting Blocks

It is possible to nest TagScript blocks within one another. Specifically, any one block section may be nested deeper but nesting must not cross between sections. This means that a block’s declaration, parameter, and payload may all be deeply nested blocks themselves, but they cannot be combined. A nested block cannot provide both the parameter AND the payload at the same time. Separately nested blocks are required in order to uphold the fundamental structure of a block (see Valid Block Structures).

Caution

Some blocks impose additional restrictions on how blocks may be nested within their parameters and payloads. One example is the “zero-depth” restriction (used by some blocks, like the IfBlock).

Check each block’s documentation for additional restrictions beyond the basic structural ones.

Where possible, the inner nested blocks are interpreted only when necessary:

{if(1>2):{command:echo reality is broken!!}|1 is still not larger than 2}

Obviously, the CommandBlock should only be interpreted if the condition for the IfBlock is true (which isn’t the case here). Not all blocks are able to avoid side effects this precisely, so it is a good idea to use an IfBlock with an easily checked condition to determine whether to run a block with side effects that should only happen under certain conditions.

You can go pretty wild with nesting blocks (this is still pretty tame)
 1from ya_tagscript import TagScriptInterpreter, adapters, blocks
 2
 3
 4class MyObject:
 5
 6    def __init__(self, the_attr: int) -> None:
 7        self.my_attr = the_attr
 8
 9
10used_blocks = [
11    blocks.AssignmentBlock(),
12    blocks.CommentBlock(),
13    blocks.IfBlock(),
14    blocks.MathBlock(),
15    blocks.RangeBlock(),
16    blocks.StrictVariableGetterBlock(),
17]
18interpreter = TagScriptInterpreter(used_blocks)
19
20# This is also a neat demonstration that variable names can use spaces, hyphens, etc.
21# in their names (though underscores are probably more readable)
22
23script = """
24{comment:Note that we're using a seed for the range block so we can guarantee the
25result (it's 52 for this seed)
26also, blocks can have multiline parameters and payloads as you can see (though comment
27blocks are simply removed from the output)}
28
29{=(random_number):{range({random-seed}):1-100}}
30The totally random number is: {random_number}
31{=(calculated):{math:{my object(my_attr)} * {random_number}}}
32The random number times our object attribute results in: {calculated}
33{if({calculated}>100000):Wow that is huge!|{math:trunc({calculated})} is a pretty neat number}
34
35And to prove that seeding the range block works:
36{=(manual_calculation):{math:52 * 1024}}
37{if({calculated}=={manual_calculation}):Seeding worked!|😅 Oops! If you get this output line, tell the dev that range seeding broke…}
38""".strip()
39
40seeds = {
41    "random-seed": adapters.IntAdapter(13579),
42    "my object": adapters.ObjectAdapter(MyObject(1024)),
43}
44
45response = interpreter.process(script, seed_variables=seeds)
46print(response.body)
47
48# The totally random number is: 52
49#
50# The random number times our object attribute results in: 53248.0
51# 53248 is a pretty neat number
52#
53# And to prove that seeding the range block works:
54#
55# Seeding worked!

Note

The term “block” is used to refer to two slightly different things:

  • subclasses of BlockABC: these are Python classes that the interpreter uses to process the string input to get the desired output

  • string structures like {declaration(parameter):payload} in scripts (which relate to a specific Python class that processes it, depending on the declaration)

A phrase such as “Use an IfBlock to make boolean checks” means to write a script that has a block like {if(1==1):success|failure}.

Context should make it obvious which one is being referred to. If you think additional clarification may be needed in a certain place, do not hesitate to open an issue on the GitHub repository.