diff --git a/Format b/Format new file mode 100644 index 0000000..98a3eeb --- /dev/null +++ b/Format @@ -0,0 +1,388 @@ +Basic Code Formatting Rules: + +0) Like most things in Alepha, these rules are "strong guidelines". When in doubt, these rules +form a baseline of good current practice. However, there's no substitute for good judgement. +Therefore, a large degree of formatting latitude is afforded to code. The primary value +being sought is clarity, not consistency. Therefore, new stylistic forms are constantly being +adopted for specific cases -- it is cumbersome (at best) to keep track of all of them here. + +Reason: Format should convey meaning. It's fine to invent new conventions. Documenting them is +good too, but it shouldn't be cumbersome to do so -- thus this file itself will always be an +incomplete representation of the format. + +Exceptions: Certain core guidelines are universally required, like tabs. + +1) Alepha code uses tab character ('\t', ASCII:0x09) for indentation. One tab per indent level. Tabs +are not to be used for alignment, except with other tabs. Tabs have no expected set width. + +Reason: Tabs permit customization of code indent level to a reader's taste via the tabwidth settings +of editors. + +Exceptions: None. The tab character as _indent only_ permits cooperation amongst those with different +visual preferences. + +2) Semicolons on all lines shall appear immediately after the last syntactic character, without a separating space, +immediately followed by a newline (or trailing comment). It is permissible put a semicolon on its own line to +facilitate a block structure where appropriate for the language. + +Examples: + +++i; +f( a, b ); + +const bool value= true + or a + or b +; + +3) Declare only one variable per "line". + +Examples: + +int x; +float *y; +int &xr= x; + +Reason: C++ declaration grammar inherits ambiguities and oddities from C. By declaring one variable per line, we sidestep this issue. + +Exceptions: None. + + +4) All bracing constructs, "(), {}, [], and <>", must have a space between the opening brace and the first +argument, and between the last argument and the closing brace. If there are no arguments then there should +be no spaces between the opening and closing brace. No space shall exist between a keyword or an entity name +and the following opening bracing construct. A newline character counts as a space for these constructs, +thus permitting block-like structure. + +Examples: + +if( someFunction( argument ) ) return 1; // Good. Notice that there is no trailing space after the "if" +std::vector< std::map< std::string, std::set< std::string > > > complexDataEntity; // Even for templates +complexDataEntity[ 10 ][ "Hello "].find( "Something" ); // Even for array-like operations. +std::cout << [](){ return "Hello World!"; }() << std::endl; // For one-lining lambdas and initializers, the rule applies. +for( int i= 0; i < 10; ++i ) std::cout << i << std::endl; // Even within a loop construct. + +Reason: Extra space can be visually helpful. Many styles encourage visual cues to code flow. + + +5) All 2-argument operators that do not modify the left hand side (binary-function operators) must have a space +on both sides of the operator, whereas data-modifying equals-based operators like ' =, +=, -=, *=, /=, ^=, |=, +&=, ~=, <<=, >>=, %= ' shall have no space to their left, thus binding them to the expression that they modify. +Note that ' !=, <=, >= ' are not assignment-like operators, but a comparison operators. Comma "operators" +shall also follow left-hand no spacing rules, as this matches their natural use in other circumstances. + +Examples: + +int a= 10; +int b= 11, c= 12; +c= ( a * b + c ) / ( a * 22 ); +a%= c / b; +if( ( a != b ) or ( c < a ) && ( a= b ) ) return true; + +Reason: Extra visual space helps. Cuddling the assignment operators helps distinguish them from comparison +operators. "<=" vs "<<=" and "==" vs "=". By having extra code-intent presented in a side-channel (spacing), +it helps prevent accidental typos and bugs. + +Exceptions: `<=` when used as a "left fat arrow" operator may be used with the spacing rules that most +make sense for their context. `<=[]` on lambdas, for example. `/` when used as a directory separator +between path components from `std::filesystem` should also omit surrounding spaces. + + +6) All unary operators shall "cuddle" their applied variable. Pointers and references shall be be declared +with the "&" or "*" attached to the variable name. + +Examples: + +int a= 10; +++a; +a--; +a= ~a; +if( !a ) return false; + +Reason: Somewhat improves readability. For declarations, although "int &" is the type of "int &x;", this +does not extend to: "int &x, y". Those are distinct types, but by banning multiple declarations per line, +we mostly avoid this issue. + + +7) Brace placement shall be "Allman/BSD"-like. Opening braces shall be on a new line, closing braces on a +new line. Braces shall be indented by the same level as the line of their "control" statement. A braced-block +that is not subordinate to a control statement shall be indented as if it had an "if( true )" control statement +above it. A do-while loop shall have the while on its own line, after the closing brace, where possible. Note +that one-line compaction is permitted, as is omitting braces for fully subsumed blocks. + +Examples: + +int +main() +{ + int x= 10; + int y= 11; + if( x < y ) + { + std::vector< int > v; + if( y % 2 ) + { + rv.push_back( v ); + } + + // Note that this if block has no parent, but it is indented like it has one. + { + std::list< int > l( v.begin(), v.end() ); + if( x == y ) for( const auto &x: stuff ) + { + std::cout << x << std::endl; + } + std::cout << l.size() << std::endl;; + } + + do + { + std::cout << v.size() << std::endl; + } + while( false ); + + } + + return 0; +} + +Exceptions: Very small functions or blocks can be subsumed into one line, for brevity. + +8) Return types shall be placed above the name of the function in definitions, but on the same line for +declarations. inline or explicit for ctors, dtors, and conversion functions shall follow this rule as well. + +Example: + +bool is_even( int x ); + +bool +is_even( int x ) +{ + return ~x & 1; +} + + +9) Template clauses for definitions shall be on their own line. For declarations, a separate line is optional. + +Examples: + +template< typename T > bool is_even( T x ); + +template< typename T > +bool +is_even( T x ) +{ + return ( x % 2 ) == 0; +} + + +10) In class definitions, public, protected, and private regions shall be indented beyond the access control +keywords, which must also be indented. In switch/case blocks, cases shall be similarly single-indented, +as too breaks shall remain single indented. All switch statements shall have a break, return, or throw +at the end of each case clause or shall use [[fallthrough]] to document fallthrough behavior, where +it is used. Default clauses are not mandatory, but are recommended. (Default clauses might be blank, +or all cases might be covered, especially when the argument is an enum.) + +Examples: + +class Foobar +{ + private: + int x; + + public: + void setX( int newx ) { x= newx; } + int getX() const { return x } + + void + operation( int val ) + { + switch( val ) + { + case 2: + x= 11; + break; + + case 11: + x= 2; + break; + + case 12: + specialCase(); + [[fallthrough]]; + + default: + x= val; + break; + } + } +}; + +struct Open +{ + int x; +}; + + +11) Continued lines shall be indented twice past the main parent's indent. When breaking a line on an operator, +the next line should begin with an operator. Spaces may also be used to line up nicely for vertical alignment +of operators; however, note that spacing alignment starts from the tab-depth of the previous line. (Never use +spaces to align to a specific tab depth.) Control structures which have a single subordinate statement +on a separate line shall enclose that line in braces. If wrapping becomes necessary with "one line" control +structures, then bracing is required. + +Example: + +int +main() +{ + std::cout << "A really long message which " + << "needed to be indented " + << "across multiple lines." << std::endl; + + std::cout << "A really long message which " + << "needed to be indented " + << "across multiple lines. " + << "This message was aligned " + << "on ostream operators." + << std::endl; + + if( condition ) action(); + + if( moreComplicatedCondition ) + { + moreComplicatedAction(); + } +} + +Reasons: It is important to know when lines have wrapped. A specific visually distinct +style helps make this stand out from other indented scenarios. By avoiding braceless +subordinates to control structures we avoid a class of silly maintenance bugs. + + +12) Initializer lists shall be treated as-if they're part of the ctor body. They get +single indented, unless the function is really short. + +Example: + +struct StringWrapper +{ + std::string string; + + explicit + StringWrapper( const std::string &s ) + : string( s ) {} + + explicit StringWrapper() : string( "no value" ) {} // A short ctor or other function can be on one-line. +}; + +Exceptions: Single line functions and ctors, and the like are sometimes good to conserve space. Trivial +implementations should take trivial amounts of space. + + +13) Private data shall be declared before the functionality of the class is declared or defined. Constructors, +copy assignment, and destructors shall be declared (or defined) before other member functions. + +Example: + +template< typename T > +class smart_ptr : boost::noncopyable +{ + private: + T *ptr; + + public: + explicit inline + smart_ptr( T *const p ) + : ptr( p ) {} + + inline ~smart_ptr() { delete p; } + + T & + operator *() const + { + return *ptr; + } + + T * + operator->() const + { + return ptr; + } +}; + +Reasons: It's easier to reason about the correctness of a class's primary invariant mechanism (construction +and destruction) when its near the variables and types it manages. Knowing the contents of a class helps to +understand about the invariants which must be maintained. Some argue this makes finding the public interfaces +harder -- but Hera classes are designed to be perused by doxygen, and thus this documentation shall serve as +a primary public usage reference. Within Hera, the only reason to read a header file is to understand more +about a particular implementation. + +Exceptions: None. + + +14) Class parents (inheritance) shall logically be on the same line as the declaration. If on another line, +must count as a continuation. + +Reasons: It helps to isolate a class's name from the parent list, which is (when you think about it) part of +the class, and needs indentation. + + +15) Preprocessor nested scopes (which sometimes do happen) shall indent, by requisite spacing, all characters +AFTER the leading "#". + +Examples: + +#ifndef SOME_THING +# ifndef SOME_OTHER_THING +# define NEITHER_THING_IS_AVAILABLE +# else +# error Must have SOME_THING if SOME_OTHER_THING is around +# endif +#endif + +Reasons: Because C preprocessor macros also follow logical scope, it helps to trace their nesting by indentation. +By indenting after the leading "#", we help distinguish C++ code from C preprocessor code. + +Exceptions: The header guards, being ubiquitous, shall not count as a level of indentation in nested preprocessor +construct + + +16) In declaration of entities, the following order of keywords shall always be followed: + +virtual explicit extern static constexpr consteval constinit inline mutable const volatile + +For integers & double: +unsigned long long int +signed short int +long double + +signed and int are each optional. + +For characters: +unsigned char +signed char +char + +Each of those char types are three distinct types -- signed or unsigned are NOT optional. + + +Examples: +struct Type +{ + virtual inline ~Type() noexcept {} // Yes, this is actually allowed. + explicit inline Type() {} + static inline const std::string string() { return "String"; } + + // Allowed, but violates the style guide: + long const unsigned volatile int virtual long inline *resource() const; + + // The same declaration, following the style guide: + virtual inline const volatile unsigned long long int *resource() const; +}; + +Reasons: C++ is very forgiving in the order of its type-and-qualifiers, but standardizing them makes finding +specific things simpler. There are many mutually exclusive keywords above, like static and virtual and explicit, +for example. Many may not know it but 'long const unsigned volatile int virtual long inline *' is a valid token +sequence for declaring a member function. Standardization gives us: 'virtual inline const volatile unsigned +long long int *', which makes the declaration type far less confusing.