-
Notifications
You must be signed in to change notification settings - Fork 11
The Doc Language
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).
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: Note: Structure Classes are missing from this chart.
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. - Note that indents need to be singular Tab characters. Spaces or double tabs are not supported.
- 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.
- We can place a star (
- 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
orobject
), - an object type/reference used by OpenSMCE (such as
Vector2
,Sprite
,SoundEvent
orCollectibleGenerator
), - 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 (
#
).
- a basic JSON type (
- We can also assign a few valid types for a field by separating them with the pipe (
|
) character, e.g.Color|ColorPalette
.
- For the type we can use:
- 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 (
-
).- You can use a few Markdown styles:
*italic*
,**bold**
,***bold italic***
and`code`
.
- You can use a few Markdown styles:
- For example, in order to define a simple string field, we can use the following:
- name (string) - The name of an object.
- This example defines an optional non-negative number field:
- spawnDelay* (number) [>=0] - Time between the consecutive particle spawns, in seconds.
- Defining objects is very easy as well:
- someObject (object) - A container for two integers and a string. - number1 (integer) - The first integer. - number2 (integer) - The second integer. - text (string) - The string value.
- As well as arrays. In this case, you define a single nameless child, which must match all of that table's elements:
- listOfNumbers (array) - A list of negative numbers. - (number) [<0] - A negative number.
Despite all lines not starting with -
being ignored, we recommend starting all comments with #
so that nothing breaks in the future!
Not every schema is that simple, so in this section, we will introduce some more interesting structures.
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.
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.
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.