diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -28,6 +28,7 @@ NamedParameterCheck.cpp NamespaceCommentCheck.cpp NonConstParameterCheck.cpp + OperatorsRepresentationCheck.cpp QualifiedAutoCheck.cpp ReadabilityTidyModule.cpp RedundantAccessSpecifiersCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.h b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.h @@ -0,0 +1,39 @@ +//===--- OperatorsRepresentationCheck.h - clang-tidy ------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H + +#include "../ClangTidyCheck.h" +#include "llvm/ADT/SmallVector.h" +#include + +namespace clang::tidy::readability { + +/// Check helps enforce consistent token representation for binary, unary and +/// overloaded operators in C++ code. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability/operators-representation.html +class OperatorsRepresentationCheck : public ClangTidyCheck { +public: + OperatorsRepresentationCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override; + std::optional getCheckTraversalKind() const override; + +private: + std::string BinaryOperators; + std::string OverloadedOperators; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_OPERATORSREPRESENTATIONCHECK_H diff --git a/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/OperatorsRepresentationCheck.cpp @@ -0,0 +1,340 @@ +//===--- OperatorsRepresentationCheck.cpp - clang-tidy +//--------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "OperatorsRepresentationCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/StringRef.h" +#include +#include +#include + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +StringRef getOperatorSpelling(SourceLocation Loc, ASTContext &Context) { + if (Loc.isInvalid()) + return {}; + + auto &SourceManager = Context.getSourceManager(); + + Loc = SourceManager.getSpellingLoc(Loc); + if (Loc.isInvalid()) + return {}; + + const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc); + return Lexer::getSourceText(TokenRange, SourceManager, Context.getLangOpts()); +} + +AST_MATCHER_P2(BinaryOperator, hasInvalidRepresentationB, BinaryOperatorKind, + Kind, llvm::StringRef, ExpectedRepresentation) { + if (Node.getOpcode() != Kind || ExpectedRepresentation.empty()) + return false; + + StringRef Spelling = + getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext()); + return !Spelling.empty() && Spelling != ExpectedRepresentation; +} + +AST_MATCHER_P2(UnaryOperator, hasInvalidRepresentationU, UnaryOperatorKind, + Kind, llvm::StringRef, ExpectedRepresentation) { + if (Node.getOpcode() != Kind || ExpectedRepresentation.empty()) + return false; + + StringRef Spelling = + getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext()); + return !Spelling.empty() && Spelling != ExpectedRepresentation; +} + +AST_MATCHER_P2(CXXOperatorCallExpr, hasInvalidRepresentationO, + OverloadedOperatorKind, Kind, llvm::StringRef, + ExpectedRepresentation) { + if (Node.getOperator() != Kind || ExpectedRepresentation.empty()) + return false; + + StringRef Spelling = + getOperatorSpelling(Node.getOperatorLoc(), Finder->getASTContext()); + return !Spelling.empty() && Spelling != ExpectedRepresentation; +} + +} // namespace + +constexpr std::array, 2U> + UnaryRepresentation{{{"!", "not"}, {"~", "compl"}}}; + +constexpr std::array, 9U> + OperatorsRepresentation{{{"&&", "and"}, + {"||", "or"}, + {"^", "xor"}, + {"&", "bitand"}, + {"|", "bitor"}, + {"&=", "and_eq"}, + {"|=", "or_eq"}, + {"!=", "not_eq"}, + {"^=", "xor_eq"}}}; + +static llvm::StringRef translate(llvm::StringRef value) { + for (const auto &[traditional, alternative] : UnaryRepresentation) { + if (value == traditional) + return alternative; + if (value == alternative) + return traditional; + } + + for (const auto &[traditional, alternative] : OperatorsRepresentation) { + if (value == traditional) + return alternative; + if (value == alternative) + return traditional; + } + return {}; +} + +static bool isSeparator(char p_char) noexcept { + switch (p_char) { + case ' ': + case '\t': + case '\r': + case '\n': + case '\0': + case '(': + case ')': + case '<': + case '>': + case '{': + case '}': + case ';': + case ',': + return true; + default: + return false; + }; +} + +static bool needEscaping(llvm::StringRef Operator) { + switch (Operator[0]) { + case '&': + case '|': + case '!': + case '^': + case '~': + return false; + default: + return true; + } +} + +llvm::StringRef getRepresentation(llvm::StringRef Config, + llvm::StringRef Traditional, + llvm::StringRef Alternative) { + const std::string TraditionalStr = ',' + Traditional.str() + ','; + if (Config.contains(TraditionalStr)) + return Traditional; + const std::string AlternativeStr = ',' + Alternative.str() + ','; + if (Config.contains(AlternativeStr)) + return Alternative; + return {}; +} + +template +static bool isAnyOperatorEnabled(llvm::StringRef Config, T &&Operators) { + for (const auto &[traditional, alternative] : Operators) { + if (!getRepresentation(Config, traditional, alternative).empty()) + return true; + } + return false; +} + +OperatorsRepresentationCheck::OperatorsRepresentationCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + BinaryOperators(Options.get("BinaryOperators", "and,or,not")), + OverloadedOperators(Options.get("OverloadedOperators", "and,or,not")) { + BinaryOperators.insert(BinaryOperators.begin(), ','); + BinaryOperators.push_back(','); + BinaryOperators.erase( + std::remove(BinaryOperators.begin(), BinaryOperators.end(), ' '), + BinaryOperators.end()); + + OverloadedOperators.insert(OverloadedOperators.begin(), ','); + OverloadedOperators.push_back(','); + OverloadedOperators.erase( + std::remove(OverloadedOperators.begin(), OverloadedOperators.end(), ' '), + OverloadedOperators.end()); +} + +void OperatorsRepresentationCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "BinaryOperators", + llvm::StringRef(BinaryOperators).trim(',')); + Options.store(Opts, "OverloadedOperators", + llvm::StringRef(OverloadedOperators).trim(',')); +} + +std::optional +OperatorsRepresentationCheck::getCheckTraversalKind() const { + return TK_IgnoreUnlessSpelledInSource; +} + +bool OperatorsRepresentationCheck::isLanguageVersionSupported( + const LangOptions &LangOpts) const { + return LangOpts.CPlusPlus; +} + +void OperatorsRepresentationCheck::registerMatchers(MatchFinder *Finder) { + + if (isAnyOperatorEnabled(BinaryOperators, OperatorsRepresentation)) { + Finder->addMatcher( + binaryOperator( + unless(isExpansionInSystemHeader()), + anyOf( + hasInvalidRepresentationB( + BO_LAnd, getRepresentation(BinaryOperators, "&&", "and")), + hasInvalidRepresentationB( + BO_LOr, getRepresentation(BinaryOperators, "||", "or")), + hasInvalidRepresentationB( + BO_NE, getRepresentation(BinaryOperators, "!=", "not_eq")), + hasInvalidRepresentationB( + BO_Xor, getRepresentation(BinaryOperators, "^", "xor")), + hasInvalidRepresentationB( + BO_And, getRepresentation(BinaryOperators, "&", "bitand")), + hasInvalidRepresentationB( + BO_Or, getRepresentation(BinaryOperators, "|", "bitor")), + hasInvalidRepresentationB( + BO_AndAssign, + getRepresentation(BinaryOperators, "&=", "and_eq")), + hasInvalidRepresentationB( + BO_OrAssign, + getRepresentation(BinaryOperators, "|=", "or_eq")), + hasInvalidRepresentationB( + BO_XorAssign, + getRepresentation(BinaryOperators, "^=", "xor_eq")))) + .bind("bo"), + this); + } + + if (isAnyOperatorEnabled(BinaryOperators, UnaryRepresentation)) { + Finder->addMatcher( + unaryOperator( + unless(isExpansionInSystemHeader()), + anyOf( + hasInvalidRepresentationU( + UO_LNot, getRepresentation(BinaryOperators, "!", "not")), + hasInvalidRepresentationU( + UO_Not, getRepresentation(BinaryOperators, "~", "compl")))) + .bind("uo"), + this); + } + + if (!isAnyOperatorEnabled(OverloadedOperators, OperatorsRepresentation) && + isAnyOperatorEnabled(OverloadedOperators, UnaryRepresentation)) + return; + + Finder->addMatcher( + cxxOperatorCallExpr( + unless(isExpansionInSystemHeader()), + anyOf( + hasInvalidRepresentationO( + OO_AmpAmp, + getRepresentation(OverloadedOperators, "&&", "and")), + hasInvalidRepresentationO( + OO_PipePipe, + getRepresentation(OverloadedOperators, "||", "or")), + hasInvalidRepresentationO( + OO_Exclaim, + getRepresentation(OverloadedOperators, "!", "not")), + hasInvalidRepresentationO( + OO_ExclaimEqual, + getRepresentation(OverloadedOperators, "!=", "not_eq")), + hasInvalidRepresentationO( + OO_Caret, getRepresentation(OverloadedOperators, "^", "xor")), + hasInvalidRepresentationO( + OO_Amp, + getRepresentation(OverloadedOperators, "&", "bitand")), + hasInvalidRepresentationO( + OO_Pipe, + getRepresentation(OverloadedOperators, "|", "bitor")), + hasInvalidRepresentationO( + OO_AmpEqual, + getRepresentation(OverloadedOperators, "&=", "and_eq")), + hasInvalidRepresentationO( + OO_PipeEqual, + getRepresentation(OverloadedOperators, "|=", "or_eq")), + hasInvalidRepresentationO( + OO_CaretEqual, + getRepresentation(OverloadedOperators, "^=", "xor_eq")), + hasInvalidRepresentationO( + OO_Tilde, + getRepresentation(OverloadedOperators, "~", "compl")))) + .bind("oo"), + this); +} + +void OperatorsRepresentationCheck::check( + const MatchFinder::MatchResult &Result) { + + SourceLocation Loc; + + if (const auto *Op = Result.Nodes.getNodeAs("bo")) + Loc = Op->getOperatorLoc(); + else if (const auto *Op = Result.Nodes.getNodeAs("uo")) + Loc = Op->getOperatorLoc(); + else if (const auto *Op = Result.Nodes.getNodeAs("oo")) + Loc = Op->getOperatorLoc(); + + if (Loc.isInvalid()) + return; + + Loc = Result.SourceManager->getSpellingLoc(Loc); + if (Loc.isInvalid() || Loc.isMacroID()) + return; + + const CharSourceRange TokenRange = CharSourceRange::getTokenRange(Loc); + if (TokenRange.isInvalid()) + return; + + StringRef Spelling = Lexer::getSourceText(TokenRange, *Result.SourceManager, + Result.Context->getLangOpts()); + StringRef TranslatedSpelling = translate(Spelling); + + if (TranslatedSpelling.empty()) + return; + + std::string FixSpelling = TranslatedSpelling.str(); + + StringRef SourceRepresentation = "an alternative"; + StringRef TargetRepresentation = "a traditional"; + if (needEscaping(TranslatedSpelling)) { + SourceRepresentation = "a traditional"; + TargetRepresentation = "an alternative"; + + StringRef SpellingEx = Lexer::getSourceText( + CharSourceRange::getCharRange( + TokenRange.getBegin().getLocWithOffset(-1), + TokenRange.getBegin().getLocWithOffset(Spelling.size() + 1U)), + *Result.SourceManager, Result.Context->getLangOpts()); + if (SpellingEx.empty() || !isSeparator(SpellingEx.front())) + FixSpelling.insert(FixSpelling.begin(), ' '); + if (SpellingEx.empty() || !isSeparator(SpellingEx.back())) + FixSpelling.push_back(' '); + } + + diag( + Loc, + "'%0' is %1 token spelling, consider using %2 token '%3' for consistency") + << Spelling << SourceRepresentation << TargetRepresentation + << TranslatedSpelling + << FixItHint::CreateReplacement(TokenRange, FixSpelling); +} + +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -32,6 +32,7 @@ #include "MisplacedArrayIndexCheck.h" #include "NamedParameterCheck.h" #include "NonConstParameterCheck.h" +#include "OperatorsRepresentationCheck.h" #include "QualifiedAutoCheck.h" #include "RedundantAccessSpecifiersCheck.h" #include "RedundantControlFlowCheck.h" @@ -100,6 +101,8 @@ "readability-misleading-indentation"); CheckFactories.registerCheck( "readability-misplaced-array-index"); + CheckFactories.registerCheck( + "readability-operators-representation"); CheckFactories.registerCheck( "readability-qualified-auto"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -120,6 +120,12 @@ Checks that all implicit and explicit inline functions in header files are tagged with the ``LIBC_INLINE`` macro. +- New :doc:`readability-operators-representation + ` check. + + Check helps enforce consistent token representation for binary, unary and + overloaded operators in C++ code. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -351,6 +351,7 @@ `readability-misplaced-array-index `_, "Yes" `readability-named-parameter `_, "Yes" `readability-non-const-parameter `_, "Yes" + `readability-operators-representation `_, "Yes" `readability-qualified-auto `_, "Yes" `readability-redundant-access-specifiers `_, "Yes" `readability-redundant-control-flow `_, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/operators-representation.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/operators-representation.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/operators-representation.rst @@ -0,0 +1,173 @@ +.. title:: clang-tidy - readability-operators-representation + +readability-operators-representation +==================================== + +Check helps enforce consistent token representation for binary, unary and +overloaded operators in C++ code. The check supports both traditional and +alternative representations of operators, such as ``&&`` and ``and``, ``||`` +and ``or``, and so on. + +By using this check, you can ensure that your code uses a consistent +representation of operators, which can improve readability, maintainability, +and consistency. + +To configure the check to enforce the traditional token representation, you can +set the `BinaryOperators` and `OverloadedOperators` options to ``&&,||,!``, +respectively. This will cause the check to warn on any occurrences of ``and``, +``or``, and ``not`` tokens in the code, and suggest using the traditional tokens +instead. + +Note that the check only warns on operator expressions, and will not warn for +declarations of overloaded operators, or code inside macros. + +Whether you choose to use the traditional or alternative token representation +is up to you, but before you choose traditional representation please read about +Alternative Token Representation. + +Alternative Token Representation +-------------------------------- + +C++ Alternative Token Representation (ATR) is a feature introduced in the C++98 +standard that allows programmers to use alternative tokens to represent some of +the operators in the C++ language. Specifically, the ATR provides alternative +spellings for some of the commonly used operators, such as ``and``, ``or``, +``not``, and others. The ATR was introduced to make C++ more readable and to +provide an alternative to the traditional operators, which some developers +found hard to read and understand. + +.. table:: Token Representation Mapping Table + :widths: auto + + =========== =========== + Traditional Alternative + =========== =========== + ``&&`` ``and`` + ``&=`` ``and_eq`` + ``&`` ``bitand`` + ``|`` ``bitor`` + ``~`` ``compl`` + ``!`` ``not`` + ``!=`` ``not_eq`` + ``||`` ``or`` + ``|=`` ``or_eq`` + ``^`` ``xor`` + ``^=`` ``xor_eq`` + =========== =========== + + +One of the main benefits of using ATR is that it can make the code more +readable and easier to understand. For example, the token ``!`` is a traditional +representation of the logical NOT operator, but it is small and can be easily +overlooked during development or code review. Moreover, the ``!`` token can be +confusing, especially when used in conjunction with other operators, such as +the bitwise NOT operator ``~``. In contrast, the alternative token ``not`` is +much more clear and can make the code more readable. + +For example, consider the following code: + +.. code-block:: c++ + + if (!a||!b) + { + // do something + } + + +While the code is correct, the use of the ``!`` token can make it harder to read +and understand. After using the ``not`` and ``or`` token, the code becomes: + +.. code-block:: c++ + + if (not a or not b) + { + // do something + } + +The use of the ``not`` and ``or`` token makes the code easier to read and +understand, especially for new developers. Furthermore, the use of the ``not`` +token can also help in cases where the logical NOT operator is used in +conjunction with other operators, such as ``&&`` or ``^``. + +In addition, this check can improve the consistency of code. By using a +consistent set of tokens throughout a codebase, it can be easier for developers +to understand and reason about the code. This can be particularly important in +larger codebases where many different developers may be working on the same +code. + +Consider the following code: + +.. code-block:: c++ + + if (a || b) + { + // do something + } + +This code uses the ``||`` token to represent the logical OR operation. While +this may be familiar to experienced developers, it can still require some +mental effort to convert the symbol into the word `or` when reading the code. +By using the ``or`` token instead, the code can be more easily read and +understood: + +.. code-block:: c++ + + if (a or b) + { + // do something + } + +In combination with clear function and variable names, code that uses +alternative token representation can be read like a well-written book, making +it easier for developers to understand and modify. + +Another benefit of using alternative token representation is that many of the +alternative tokens are keywords in C++. This means that editors and development +environments are able to highlight them, making the code even more readable. +Additionally, alternative tokens are easier to distinguish from other +identifiers in code, further improving code readability and maintainability. + +It is important to note that using alternative token representation has some +drawbacks as well. One of the main drawbacks is that this functionality is not +compatible with C. Alternative tokens are defined as macros in ``iso646.h`` in C, +but they are not supported natively in the C language. Furthermore, some +developers may be more familiar with the traditional operators, which can make +the code harder to read and understand if alternative tokens are used. +Additionally, some developers may find the traditional operators more concise +and easier to use, especially in cases where code size is a concern. + +Despite this limitation, the use of alternative token representation is widely +accepted and recommended in C++ programming. In fact, the C++ standard +explicitly allows the use of alternative tokens as an alternative to traditional +tokens. The standard also recommends the use of alternative tokens when they +make the code more readable and maintainable. + +In conclusion, alternative token representation can be a powerful tool for +improving the readability and maintainability of C++ code. By using clear and +consistent tokens, developers can make their code easier to read and understand, +leading to fewer bugs and faster development times. While there are some +potential drawbacks, the benefits of alternative token representation make it +a feature that is well worth considering for any C++ project. + +Don't wait, +**Unlock the Power of Clarity - Enforce Alternative Token Representation with +Clang-Tidy for Crisp and Consistent Code!** + + +Options +------- + + +.. option:: BinaryOperators + + This option allows you to specify a comma-separated list of binary + operators for which you want to enforce specific token representation. + Configuration can be mixed, for example: `and,||,not`. + The default value is `and,or,not`. + +.. option:: OverloadedOperators + + This option allows you to specify a comma-separated list of overloaded + operators for which you want to enforce specific token representation. + Configuration can be mixed, for example: `and,||,not`. + The default value is `and,or,not`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/operators-representation.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/operators-representation.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/operators-representation.cpp @@ -0,0 +1,43 @@ +// RUN: %check_clang_tidy %s readability-operators-representation %t + +bool wrongCode(int a, int b, int c) +{ +// CHECK-MESSAGES: :[[@LINE+4]]:13: warning: '!' is a traditional token spelling, consider using an alternative token 'not' for consistency [readability-operators-representation] +// CHECK-MESSAGES: :[[@LINE+3]]:16: warning: '&&' is a traditional token spelling, consider using an alternative token 'and' for consistency [readability-operators-representation] +// CHECK-MESSAGES: :[[@LINE+2]]:22: warning: '||' is a traditional token spelling, consider using an alternative token 'or' for consistency [readability-operators-representation] +// CHECK-FIXES: {{^}} return (not a and b) or c;{{$}} + return (!a && b) || c; +} + +bool wrongBit(unsigned a, unsigned b, unsigned c) +{ + return (a & b) | c; +} + +bool correctCode(int a, int b, int c) +{ + return (not a and b) or c; +} + +bool correctBit(unsigned a, unsigned b, unsigned c) +{ + return (a bitand b) bitor c; +} + +struct Class +{ + bool operator!() const; + bool operator&&(const Class&); + bool operator&(const Class&); + bool operator||(const Class&); + bool operator|(const Class&); +}; + +bool testOverloadedOperators() { + Class a, b; +// CHECK-MESSAGES: :[[@LINE+4]]:15: warning: '&&' is a traditional token spelling, consider using an alternative token 'and' for consistency [readability-operators-representation] +// CHECK-MESSAGES: :[[@LINE+3]]:27: warning: '||' is a traditional token spelling, consider using an alternative token 'or' for consistency [readability-operators-representation] +// CHECK-MESSAGES: :[[@LINE+2]]:58: warning: '!' is a traditional token spelling, consider using an alternative token 'not' for consistency [readability-operators-representation] +// CHECK-FIXES: {{^}} return (a and b) or (a or b) or (a | b) or (a & b) or not a;{{$}} + return (a && b) or (a || b) or (a | b) or (a & b) or !a; +}