This is a style guide for the ZScript documentation to encourage best-practice coding with it, focused on clarity and consistency of formatting. It is also intended to be a general style guide for writing new code, but this purpose is ancillary and can be ignored. Within the abstract of each component of style will be rationale for choices made.
ZScript is case insensitive, however this guide aims to give a way to consistently capitalize all identifiers in the language. Due to case insensitivity, it may be difficult to apply these guidelines to real code, but nonetheless a consistent capitalization style should be followed. Do note that you must not rely on case differences in identifiers due to this.
Capitalize the first letter of each word in an identifier. Acronyms over 2
characters in length are considered whole words, so for instance prefer
IoStream. Acronyms of one
character also count, so prefer
Printf. Members with
prefixes in unmodifiable code must not capitalize the
For identifiers of parameter names and local scope variables do not capitalize
the first word. In these cases, prefer
IOStream. The same for acronyms of one character, so
NChars. (Note that the former two are malformed
names, however. See the “Word Choice” section for more information.)
Constants (declared with
static const, or
enum) must be all
uppercase, and must separate words with underscores.
Argument names in base archive methods may be renamed, but arguments with defaults may not be renamed as they are part of the API.
Case insensitivity in programming is generally regarded as bad practice because the lack of strictness is often a failing in language design. It is not imperative here to reason for or against case insensitivity, however. Code within this style is to be written as uniformly as possible, therefore capitalization is also to be uniform. It is the user's choice whether to follow this rule or not, just as any other, but for authors of this documentation the sole imperative is consistency.
This style of acronym capitalization has been chosen so as to defer to prior art, primarily C#‘s standard, which has been in use for over a decade now. The purpose of not capitalizing more than two characters is to make it more natural to read acronyms where normally they are forced to be next to other capitalized words.
Not capitalizing the first word of parameter and local scope variables is also decided by deferring to prior art. The majority of programming style guides written advocate for this. The purpose in ZScript is primarily moot due to case insensitivity, but we apply these rules to make reading easier and more consistent with most other programming languages that have existed.
Capitalizing constants and enumerations is an artifact of the way they are declared in ZDoom, and also in the original Linux Doom source code. This is extended to static arrays for consistency.
Flags were capitalized in Linux Doom due to being constants, and internally
within ZDoom they are still constants, but due to the style of ZScript using
full capitalization appears inconsistent. This is especially true due to the
m prefixes in places within ZScript's standard library.
In new identifiers, do not add underscores anywhere within the identifier, unless explicitly stated in this style guide or necessary because of unmodifiable code (such as that in GZDoom's base archive.) Do not use Hungarian notation.
Do not use keywords or types as names except for the identifier “
not, for instance, declare
string Class;, even though the language will allow
you to do so, although
string Name; is fine.
It is generally favorable to use shortened terminology, for instance
CanMoveVertically. Try to make names readable in English, however:
CanThrowItem instead of
In the same vein, use abbreviations where conventional, but avoid them where
GetMapObject but also
The forbidding of underscores and Hungarian notation are in accordance with ZDoom's coding style. These choices are also present in other prior art, but the main deciding factor is that of the engine itself.
The usage of most keywords in variable names is allowed in ZScript because the parser considers most identifiers to be contextual. In many contexts, keywords are allowed in places they shouldn't be by most standards. Besides the precedence of prior art, it appears that the engine itself also avoids (ab)using this relaxation of context.
The engine and this style do, however, allow use of the identifier “
variables and members because it is a frequently used word and would be absurd
Shortened terminology such as abbreviations or contractions are favored due to long-standing conventions within ZDoom's source code. This is also historically relevant because of engines such as Unreal Engine which ZDoom takes inspiration from. Common terminology which may be shortened can be found on the Doom Wiki.
Always name methods with verbs or verb phrases, such as “
CompareTo.” Always try to make names forward compatible, i.e. such that they
will likely not conflict with new functions when they are added to the engine.
In mods, this can even involve prefixing method names with one unique to the
mod. Avoid violent words such as
Kill, except where
literally applicable. Prefer for instance
Always name members with nouns, noun phrases or adjectives. Boolean values
should often be prefixed with
Can, and other existential
present-tense verbs. All members of class types should be prefixed with
despite rules against Hungarian notation and underscores. Try to name members
productively rather than vaguely, instead of
Forwards compatibility is necessary primarily in mods. In this documentation it should be taken into account when writing example code, often when writing examples that involve inheriting from a class in the engine. ZScript will hard error on load if there is more than one definition of a function, so it is necessary to never have a conflict in names, including capitalization-wise.
The purpose of avoiding violent words in method names is to not unnecessarily
invoke potentially uncomfortable or triggering imagery. In much of the base
class code, it is literally the action being taken, as Doom is indeed a violent
game. In these cases it is entirely normal to use such verbs. An example of
when this is entirely wasteful is ZDoom's garbage collector (which is exposed
with different naming conventions) in which the flag for objects marked for
finalization upon the next collection cycle is named
EuthanizeMe. This kind
of wording is certainly jocular and potentially amusing, but a ‘funny’ name
such as this serves no real purpose, and could easily be re-worded to prevent
Members are prefixed with
m_ in order to defuse potential clashes with local
variables, since case sensitivity cannot defuse them. The alternative to this
is to prefix all member variable accesses with
self.. However, this is
potentially excessive and is not entirely productive in writing short and
readable examples. This also provides a layer of forward compatibility, as
ZDoom does not use
m_ prefixes anywhere within its code. For the opposite
reason, as structure types cannot inherit, there is no need to prefix their
members, and so this prefix is omitted within them.
Use tabs with a width of 3 characters for indentation. Indent at each block,
but do not indent
case labels. Align all code to 80 columns.
Write only one statement or declaration per line, except in the case of multiple-assignment operations, in which case pairing all of the related declarations and the statement on the same line is allowed as long as it does not exceed 80 columns.
Always add one blank line between separate method definitions, and between
member declarations and member definitions. Add one blank line between local
variable declarations and statements, unless specified otherwise. Add one blank
line between conditional statements, except the
else block of an
Do not place a space before the parentheses of a conditional or loop statement
for. Always write
if() and not
Always place opening braces on their own line. Using braces is not necessary
when there is a single sub-statement, for instance with
if(x) y = z;.
The convention of 3 spaces for indentation comes from Eternity Engine's style guideline. There is no other reason for this decision, other than it is pleasing to the eye while not being excessive. Hardware tabs are used instead of spaces in order to allow for user configuration, increasing accessibility. The indentation and blank line rules are generally the same as the majority of C-like language style guidelines.
Alignment to 80 columns is for the purpose of reading the raw documentation
text under standard size Linux terminals. This is useful, for instance, when
reading diff files under the
git console client. This guideline can generally
be ignored outside of this documentation.
Writing multiple-assignment operations all on one line serves the purpose of grouping all of the relevant information together. In most languages with multiple assignment, you are able to do the declaration and assignment in one line, so this crudely mirrors a more well implemented language syntax.
Placing opening braces on their own line greatly increases code readability, especially for people hard of sight. Other styles make it easy to confuse where a block begins and create a less clear visual line between the start and end. The purpose of this is to increase readability of this documentation, and not to make code look more pleasing.
Do not use block comments, except for the purpose of example or code that will intentionally not work. Comments may be placed anywhere except at the end of the opening line of a conditional or loop statement, the opening brace of a block, or the ending brace of a block. Always use proper case and punctuation when writing comments. Always put one space between the comment delimiter and comment text.
Block comments are mostly an adage of old programming languages made to be printed on paper. In modern times they are most useful for commenting out large blocks of code. They are rather clumsy to format well, and so single-line comments are to be used instead of them. One could argue that if a consistent formatting style was added, this would not be a problem. However, it is generally easier and more readable to just use line comments.
let declarations when the type of the object will be obvious from what it
is assigned. Do not use them if it is not obvious, or in example when it is
necessary to proclaim the type to make reading easier.
Don't use integer types except for
int except in example. Similarly, do not
use float types except for
double except in example.
No restrictions are placed on the usage of parentheses. Always place spaces
between the operands of binary expressions (
1 + 1) but never unary
Do not place semicolons at the end of
struct definitions. However, always place commas at the end of the last enumeration variant.
Integer and floating-point types other than
double are primarily
for internal use. They can be used by user types, but often it's not necessary
and can be harmful.
Placing semicolons at the end of top-level items is not meant to be used by end users. Its main purpose is to make ZScript easier to port from C++. On the other hand, placing a comma at the end of the last enumeration in a set is a common practice, as it allows the user to place more variants without touching much.