MAME Coding Conventions: Difference between revisions

From MAMEDEV Wiki
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This page is WIP. Please don't edit it until this notice is removed.
MAME source code should be viewed and edited with your editor set to use four spaces per tab. Tabs are used for initial indentation of lines, with one tab used per indentation level. Spaces are used for other alignment within a line.


MAME is a project that has had many contributors from many different backgrounds. Throughout its history, there have never really been any kind of formal coding conventions defined, although thanks to imitation, there is at least a glimmer of consistency.
Some parts of the code follow [https://en.wikipedia.org/wiki/Indent_style#Allman_style Allman style]; some parts of the code follow [https://en.wikipedia.org/wiki/Indent_style#K.26R_style K&R style] — mostly depending on who wrote the original version. '''Above all else, be consistent with what you modify, and keep whitespace changes to a minimum when modifying existing source.''' For new code, the majority tends to prefer Allman style, so if you don't care much, use that.


In general, a codebase with consistent conventions is easier to understand than one with varying conventions. However, trying to impose a strict order on a project of this magnitude is certainly taking things too far.
All contributors need to either add a standard header for license info (on new files) or inform us of their wishes regarding which of the following licenses they would like their code to be made available under: the [http://opensource.org/licenses/BSD-3-Clause BSD-3-Clause] license, the [http://opensource.org/licenses/LGPL-2.1 LGPL-2.1], or the [http://opensource.org/licenses/GPL-2.0 GPL-2.0].


If you categorize the MAME codebase, you can look at it like this:
The guidelines below are not hard-and-fast rules, but more a set of guidelines describing conventions used in MAME source.
* OS-specific code (OSD)
* MAME "core" code
* CPU cores
* Sound engines
* Game drivers
 
The first two pieces (the core and OSD) are in general only handled by a small group of developers, while the remaining pieces (drivers and CPU/sound cores) come from a much broader audience. Furthermore, the drivers and CPU/sound cores all interact to some degree with core and OSD pieces below them, so the most benefit from code clarity and consistency comes from making the core and OSD pieces consistent.
 
With that in mind, below is an outline some of the key coding conventions currently in use in the core and OSD layers. If you are modifying code in these layers and wish to have it accepted upon submission, you would do well to keep to these guidelines. (In fact, if you are modifying any file in any project, you should adopt the conventions of that file/project, rather than just stuffing your own inconsistent style in the middle of something else. I can't believe how many people just ignore the existing styles and jam their own style in the middle.)
 
One more thing. Keep in mind that coding conventions are like religion: they are often strongly-held beliefs with little factual justification to back them up. You may disagree with them. Heck, even I disagree with a few of them. But they are the conventions that are used. Deal with it.


== Naming ==
== Naming ==
* function, method, and variable names are named using the <code>lower_under_convention</code>
* class, function, method, and variable names use the <code>lower_under_convention</code>
* static member functions should have a static_ prefix (e.g., <code>static_timer_callback</code>)
* not everyone uses scope prefixes, but if you're going to, please follow this convention:
* member variables within a class should have a standard prefix, as follows:
** instance member variables should have an m_ prefix (e.g., <code>m_device</code>, <code>m_config</code>, etc.)
** normal variables should have an m_ prefix (e.g., <code>m_device</code>, <code>m_config</code>, etc)
** static members should have an s_ prefix (e.g., <code>s_device_table</code>, etc.)
** static members should have an s_ prefix (e.g., <code>s_device_table</code>)
** global variables should have a g_ prefix (e.g., <code>g_profiler</code>, etc.)
** static constant members should have a k_ prefix (e.g., <code>k_maximum_items</code>)
** file scope variables (file statics or anonymous namespace) should have an f_ prefix (e.g., <code>f_ntfs_offset</code>, etc.)
** static member functions may have a static_ prefix (e.g., <code>static_timer_callback</code>)
* macros, enum items, and #defined constants should be named using the <code>ALL_CAPS_UNDER_CONVENTION</code>
* macros, enum items, and #defined constants should be named using the <code>ALL_CAPS_UNDER_CONVENTION</code>
**static constant members should be treated as constants and should be in ALL_CAPS_UNDER_FORMAT (e.g., <code>MAXIMUM_ITEMS</code>)
* template parameters should be named using the <code>PascalCaseConvention</code>
* constants which are part of a group should have a common prefix; example: <code>enum { ADDRESS_SPACE_PROGRAM, ADDRESS_SPACE_DATA, ADDRESS_SPACE_IO };</code>
* constants which are part of a group should have a common prefix; example: <code>enum { ADDRESS_SPACE_PROGRAM, ADDRESS_SPACE_DATA, ADDRESS_SPACE_IO };</code>
* prefer descriptive variable names (<code>sampnum</code>, <code>memoffset</code>) over single-letter names (<code>i</code>, <code>j</code>)
* prefer descriptive variable names (<code>sampnum</code>, <code>memoffset</code>) over single-letter names (<code>i</code>, <code>j</code>)
* never use the prefix "my" for anything; it's not "myobject", just use "object"


== Comments ==
== Comments ==
* comments in the code are preferred to be <code>// C++-style comments</code>, though <code>/* standard C-style comments */</code> are still very common
* single-line comments should use <code>// C++ line comment style</code>
* each function should have a comment preceding it that briefly describes what that function does
* larger block comments should use <code>/* C comment style */</code> to make editing and reformatting easier
* each file should begin with a header that includes information about the licensing of that file; if no licensing information is given, the standard MAME license is assumed
* interface class and function declarations should ideally have [https://www.stack.nl/~dimitri/doxygen/manual/docblocks.html Doxygen]-format comments


== Spacing ==
== Spacing ==
* a space should be used between binary and trinary operators &mdash; example: <code>a + b / 2</code>
* indenting should follow scope blocks, one level of indent per scope level
* spaces should '''not''' be used around parentheses in expressions &mdash; example: <code>(((i + j) * k) >> m)</code>
* use extra indent for statement continuation to make it easily distinguishable from a scope block
* spaces should '''not''' be used between a function and its parameters &mdash; example: <code>function(param1, param2)</code>
* no space between the function name and opening parenthesis of argument list: <code>std::strlen(param[1])</code>
* a space should be used between keywords (if, while, for) and their arguments &mdash; example: <code>for (x = 0; x < 10; x++)</code>
* space between flow control keyword and opening parenthesis of expression: <code>for (x = 0; x < 10; x++)</code>
* opening/closing braces should be on their own line, and should be indented to align with the start of the statement that introduces them
* no space between unary unary operators and operands: <code>*it++ = -value</code>
* two blank lines should separate the end of a function from the start of the next function
* spaces between binary/ternary operators and operands: <code>flag ? (a + 1) : b</code>
* no space between parenthesis/bracket and the inner expression: <code>(((i + j) * k) >> m)</code>
* use blank lines to visually separate logical blocks; use more consecutive blank lines to separate bigger logical blocks


== Expressions ==
== Scoping ==
* do not use parentheses with '''return''', it is not a function &mdash; example: <code>return 0;</code>
* in general, keep scoping as tight as possible &mdash; tighter scope makes code easier to analyse
* do not overuse parentheses except to clarify a statement &mdash; example: <code>if (a >= 10 && a < 20)</code>
* keep members private to protected if possible and provide accessors for externally visible state
* always use NULL (not 0) when working with pointers
* use protected inheritance if the base class/struct isn't part of the interface
* make comparisons against NULL explicit: <code>if (ptr != NULL)</code>
* declare constants inside the public section of classes they're relevant to
* make comparisons against 0 explicit: <code>if (strcmp(string1, string2) == 0)</code>
* restrict local variable scope to parts of the function where they're needed needed
* ''don't'' make comparisons against boolean values explicit: <code>val = (a == b); if (val)...</code>
* use anonymous namespaces for things that shouldn't be visible outside a single source file


== Language conventions ==
== Language conventions ==
* prefer references over pointers, using pointers primarily in situations where NULL is a valid option
* prefer references over pointers, using pointers primarily in situations where '''nullptr''' is a valid option
* use '''static''' and '''const''' keywords aggressively where appropriate
* use '''static''' and '''const''' keywords aggressively where appropriate
* create typedefs for function pointers; example: <code>typedef void (*my_callback_func)(UINT32 param);</code>
* use <code>std::function</code> for callbacks when performance isn't critical, or MAME delegates when it is
* make calls through function pointers explicit &mdash; example: <code>(*funcptr)(a, b)</code>
* wherever possible, use '''enum''' instead of a macro
* wherever possible, use '''enum''' instead of a macro
* used scoped '''enum''' (<code>enum class</code>) where a closed set of values are valid
* wherever possible, use '''inline''' functions instead of macros
* wherever possible, use '''inline''' functions instead of macros
* wherever possible, use templates instead of macros
* wherever possible, use templates instead of macros
* macros that look like functions should be wrapped with <code>do { &lt;macrobody&gt; } while (0)</code>
* macros that look like functions should be wrapped with <code>do { &lt;macrobody&gt; } while (0)</code>
* use rvalue references and universal references when it could help performance
* ensure non-POD types used in vectors/deques are movable to avoid performance issues
* don't abuse '''auto''' type &mdash; it is useful for template argument dependent types, iterators and proxies, but it can be dangerous and doesn't help readability/maintainability for locals where you know the type
* use anonymous namespaces to suppress symbol export &mdash; it works on all types of symbols including class members, it encourages grouping of non-exported classes/functions/globals, it avoids visual clutter of the <code>static</code> prefix on each declaration/definition, and it reduces overloading of the meaning of the <code>static</code> keyword


== Variables ==
== Variables ==
* avoid declaring static variables inside a function scope &mdash; these are really global variables and belong at the top of the module
* avoid declaring static variables inside a function scope &mdash; these are really global variables and belong at the top of the module
* declare all global variables at the top of the file
* declare all global variables at the top of the file
* use the MAME-defined types: <code>INT8</code>, <code>UINT8</code>, <code>INT16</code>, <code>UINT16</code>, <code>INT32</code>, <code>UINT32</code>, <code>INT64</code>, <code>UINT64</code>


== Header Files ==
== Header Files ==
Line 80: Line 76:
* function prototypes in header files generally do not use an '''extern''' qualifier
* function prototypes in header files generally do not use an '''extern''' qualifier
* all header files should support reinclusion; this is done by adding the following to the top of each header
* all header files should support reinclusion; this is done by adding the following to the top of each header
#ifndef MAME_MODULE_FILENAME_H
#define MAME_MODULE_FILENAME_H
  #pragma once
  #pragma once
 
#ifndef __FILENAME_H__
#define __FILENAME_H__
and adding
and adding
  #endif /* __FILENAME__H__ */
  #endif // MAME_MODULE_FILENAME_H
to the end. Note that <code>#pragma once</code> is provided because it is generally faster when supported. Since not all compilers do support it, the <code>#ifndef/#define</code> methods are retained as a fallback.
to the end. Note that <code>#pragma once</code> is provided because it may faster when supported. Since not all compilers do support it, the <code>#ifndef/#define</code> methods are retained as a fallback.


== Source Files ==
== Source Files ==
Line 101: Line 97:
** externally referenced functions
** externally referenced functions
** internal functions (in same order as prototyped)
** internal functions (in same order as prototyped)
Back to [[How MAME Works]]

Latest revision as of 12:06, 24 July 2017

MAME source code should be viewed and edited with your editor set to use four spaces per tab. Tabs are used for initial indentation of lines, with one tab used per indentation level. Spaces are used for other alignment within a line.

Some parts of the code follow Allman style; some parts of the code follow K&R style — mostly depending on who wrote the original version. Above all else, be consistent with what you modify, and keep whitespace changes to a minimum when modifying existing source. For new code, the majority tends to prefer Allman style, so if you don't care much, use that.

All contributors need to either add a standard header for license info (on new files) or inform us of their wishes regarding which of the following licenses they would like their code to be made available under: the BSD-3-Clause license, the LGPL-2.1, or the GPL-2.0.

The guidelines below are not hard-and-fast rules, but more a set of guidelines describing conventions used in MAME source.

Naming

  • class, function, method, and variable names use the lower_under_convention
  • not everyone uses scope prefixes, but if you're going to, please follow this convention:
    • instance member variables should have an m_ prefix (e.g., m_device, m_config, etc.)
    • static members should have an s_ prefix (e.g., s_device_table, etc.)
    • global variables should have a g_ prefix (e.g., g_profiler, etc.)
    • file scope variables (file statics or anonymous namespace) should have an f_ prefix (e.g., f_ntfs_offset, etc.)
    • static member functions may have a static_ prefix (e.g., static_timer_callback)
  • macros, enum items, and #defined constants should be named using the ALL_CAPS_UNDER_CONVENTION
    • static constant members should be treated as constants and should be in ALL_CAPS_UNDER_FORMAT (e.g., MAXIMUM_ITEMS)
  • template parameters should be named using the PascalCaseConvention
  • constants which are part of a group should have a common prefix; example: enum { ADDRESS_SPACE_PROGRAM, ADDRESS_SPACE_DATA, ADDRESS_SPACE_IO };
  • prefer descriptive variable names (sampnum, memoffset) over single-letter names (i, j)

Comments

  • single-line comments should use // C++ line comment style
  • larger block comments should use /* C comment style */ to make editing and reformatting easier
  • interface class and function declarations should ideally have Doxygen-format comments

Spacing

  • indenting should follow scope blocks, one level of indent per scope level
  • use extra indent for statement continuation to make it easily distinguishable from a scope block
  • no space between the function name and opening parenthesis of argument list: std::strlen(param[1])
  • space between flow control keyword and opening parenthesis of expression: for (x = 0; x < 10; x++)
  • no space between unary unary operators and operands: *it++ = -value
  • spaces between binary/ternary operators and operands: flag ? (a + 1) : b
  • no space between parenthesis/bracket and the inner expression: (((i + j) * k) >> m)
  • use blank lines to visually separate logical blocks; use more consecutive blank lines to separate bigger logical blocks

Scoping

  • in general, keep scoping as tight as possible — tighter scope makes code easier to analyse
  • keep members private to protected if possible and provide accessors for externally visible state
  • use protected inheritance if the base class/struct isn't part of the interface
  • declare constants inside the public section of classes they're relevant to
  • restrict local variable scope to parts of the function where they're needed needed
  • use anonymous namespaces for things that shouldn't be visible outside a single source file

Language conventions

  • prefer references over pointers, using pointers primarily in situations where nullptr is a valid option
  • use static and const keywords aggressively where appropriate
  • use std::function for callbacks when performance isn't critical, or MAME delegates when it is
  • wherever possible, use enum instead of a macro
  • used scoped enum (enum class) where a closed set of values are valid
  • wherever possible, use inline functions instead of macros
  • wherever possible, use templates instead of macros
  • macros that look like functions should be wrapped with do { <macrobody> } while (0)
  • use rvalue references and universal references when it could help performance
  • ensure non-POD types used in vectors/deques are movable to avoid performance issues
  • don't abuse auto type — it is useful for template argument dependent types, iterators and proxies, but it can be dangerous and doesn't help readability/maintainability for locals where you know the type
  • use anonymous namespaces to suppress symbol export — it works on all types of symbols including class members, it encourages grouping of non-exported classes/functions/globals, it avoids visual clutter of the static prefix on each declaration/definition, and it reduces overloading of the meaning of the static keyword

Variables

  • avoid declaring static variables inside a function scope — these are really global variables and belong at the top of the module
  • declare all global variables at the top of the file

Header Files

  • the preferred order of definitions in a header file is:
    • standard header
    • reinclusion protection (see below)
    • includes
    • debugging flags
    • constants
    • type definitions
    • macros
    • global variables
    • function prototypes
    • and inline functions
  • function prototypes in header files generally do not use an extern qualifier
  • all header files should support reinclusion; this is done by adding the following to the top of each header
#ifndef MAME_MODULE_FILENAME_H
#define MAME_MODULE_FILENAME_H
#pragma once

and adding

#endif // MAME_MODULE_FILENAME_H

to the end. Note that #pragma once is provided because it may faster when supported. Since not all compilers do support it, the #ifndef/#define methods are retained as a fallback.

Source Files

  • the preferred order of code in a source file is:
    • standard header
    • includes
    • debugging flags
    • constants
    • type definitions
    • macros
    • global variables (both static and global)
    • internal function prototypes
    • inline functions
    • externally referenced functions
    • internal functions (in same order as prototyped)

Back to How MAME Works