Skip to content

The Doc Language

jakubg1 edited this page May 16, 2024 · 6 revisions

With growing needs for thorough explanation of the files used by games, and a few places to put them at the same time (such as JSON schemas and HTML documentation), I've created a simple language which makes making documentation much easier - Doc Language (or DocLang for short).

Overview

The Doc Language uses *.docl as its file format. Before we delve into this file, it's worth knowing the entire flow of the documentation ecosystem. This ecosystem is comprised of the following components:

  • Doc Language Files - located in doc/game/data/*.docl and subfolders - are the primary source of all documentation information.
  • JSON Schema Files - located in schemas/*.json and subfolders - are referenced by the files which you actually modify inside a game and are used to validate data in Visual Studio Code.
  • HTML Documentation Files - located in doc/game/out/*.html, not included in the repository - are HTML sites designed to easily browse the game documentation without the need of having an IDE.
  • Structure Classes - located in src/Configs/*.lua - are Lua classes which are part of the game code. They represent the data of a matching JSON structure, however with actual assets and other classes like Expressions baked into it, instead of raw references. They also are designed to handle backward compatibility with future versions of the engine starting from Beta 5.0.0, so the game code does not get messy elsewhere.
  • Documentation Generator - located in doc/game/generate.py is a Python script which converts the Doc Language Files into JSON Schema Files, HTML Documentation Files [upcoming] and Structure Classes [upcoming], using the Documentation Generator Data. It's the heart of the documentation system.
  • Documentation Generator Data (also called DocLangHTML) - located in doc/game/data.txt is meant to be used for additional HTML Documentation pages which do not convey data for Doc Language Files, but the future purpose of it remains unknown for now. It will probably be deleted and superseded with more tags for Doc Language Files.

The flow can be also summarized in the following chart: generator_chart Note: Structure Classes are missing from this chart.

Doc Language Files

Let's now look at the syntax of the files. It's really simple, kind of like Python!

  • All lines which contribute to the JSON Schema Files start with a dash (-), preceded by a number of indents to indicate a hierarchy tree.
  • Next, we give a name for the value, unless we are describing the root object, or the element of an array.
    • We can place a star (*) right after the name to indicate that this field is optional and may not exist in the file.
  • Now, we need to assign a type for that value. We put the type in round brackets, like so: (number).
    • For the type we can use:
      • a basic JSON type (number, integer, string, boolean, array or object),
      • an object type/reference used by OpenSMCE (such as Vector2, Sprite, SoundEvent or CollectibleGenerator),
      • an expression form by applying a % prefix (%number, %integer, %string, %boolean or %Vector2) [upcoming],
      • a reference to another file by applying a $ prefix (e.g. $level_set_entry),
      • or a reference to itself (#).
    • We can also assign a few valid types for a field by separating them with the pipe (|) character, e.g. Color|ColorPalette.
  • Numbers (and integers) can also have only selected ranges allowed, by specifying them in square brackets, e.g. [>0], [<=4] or [>=-1,<=1].
  • At last, we provide a description to an element by separating it with a space, dash and another space (-).
  • For example, this line:
    - spawnDelay* (number) [>=0] - Time between the consecutive particle spawns, in seconds.
    
    defines an optional field called spawnDelay, which can be any non-negative number and is described as "Time between the consecutive particle spawns, in seconds.". You can use Markdown in the descriptions, by the way!

Despite all lines not starting with - being ignored, we recommend starting all comments with # so that nothing breaks in the future!

Complex structures

Not every schema is that simple, so in this section, we will introduce some more interesting structures.

Enums

Enums are numbers/strings which have only a few particular values allowed, and each of the values has a meaning. To specify an enum, define a number, integer or a string and then define all of the available values as its children, such as:

- exampleEnum (string) - Decides how stuff happens.
  - "none" - It doesn't happen at all.
  - "fixed" - It happens at fixed intervals.
  - "random" - It happens at random intervals.

Type-variant objects

Imagine you have a JSON object, fields of which are dependant on the type, for example:

{
  "type": "none"
}
{
  "type": "fixed",
  "interval": 10
}
{
  "type": "random",
  "intervalMin": 5,
  "intervalMax": 15
}

You don't want intervalMin and intervalMax fields when the type is "fixed" or "none", or interval when the type is "random". This is where type-variant objects come in - they allow to define the list of fields depending on another field. To create it, specify an object with the following clause:

{type: Type description}

You can specify any name for your key variable, but type usually fits the situation most. Then, specify the allowed values for it, similarly to enums. Each of the allowed values can have their own children, which will be the additional fields depending on whether the key variable is equal to that value.

Full example of the above structure:

- (object) {type: The interval type.} - Decides how stuff happens.
  - "none" - It doesn't happen at all.
  - "fixed" - It happens at fixed intervals.
    - interval (number) [>0] - Every how much time stuff happens.
  - "random" - It happens at random intervals.
    - intervalMin (number) [>0] - Every at least how much time stuff happens.
    - intervalMax (number) [>0] - Every at most how much time stuff happens.

Objects with names matching a Regex pattern (or any name)

There are rare cases where you want to allow any name for keys, or only allow particular names, for example when specifying data for particular spheres elsewhere, where you want to only ever allow positive and negative integers.

To specify a regex pattern, you need to create an object and use the

<<regex_expr>>

in your object definition. If you do that, you can only specify one child which applies for all values in that object. You cannot force other definitions for particular values.

An example of an object which allows any name for it:

- stuff (object) <<^.*$>> - A container for things, keyed by name.
  - (object) - A single thing.
    - a (number) - The first number of that thing.
    - b (number) - The second number of that thing.