From 077df4a0848b6c791c83215af34dddbc13517b34 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Tue, 24 Mar 2020 17:26:02 +0100 Subject: [PATCH] C++, support alternate spellings of operators Fixes sphinx-doc/sphinx#7367 --- CHANGES | 1 + sphinx/domains/cpp.py | 76 +++++++++++++++++++++------------- tests/test_domain_cpp.py | 89 +++++++++++++++++++++++++++++----------- 3 files changed, 115 insertions(+), 51 deletions(-) diff --git a/CHANGES b/CHANGES index 4d99e2c59..652db65ab 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,7 @@ Bugs fixed ---------- * #7370: autosummary: raises UnboundLocalError when unknown module given +* #7367: C++, alternate operator spellings are now supported. Testing -------- diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7f7558cb3..b5d9d7b76 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -306,6 +306,7 @@ _operator_re = re.compile(r'''(?x) | ->\*? | \, | (<<|>>)=? | && | \|\| | [!<>=/*%+|&^~-]=? + | (\b(and|and_eq|bitand|bitor|compl|not|not_eq|or|or_eq|xor|xor_eq)\b) ''') _fold_operator_re = re.compile(r'''(?x) ->\* | \.\* | \, @@ -464,37 +465,37 @@ _id_operator_v2 = { # '-(unary)' : 'ng', # '&(unary)' : 'ad', # '*(unary)' : 'de', - '~': 'co', + '~': 'co', 'compl': 'co', '+': 'pl', '-': 'mi', '*': 'ml', '/': 'dv', '%': 'rm', - '&': 'an', - '|': 'or', - '^': 'eo', + '&': 'an', 'bitand': 'an', + '|': 'or', 'bitor': 'or', + '^': 'eo', 'xor': 'eo', '=': 'aS', '+=': 'pL', '-=': 'mI', '*=': 'mL', '/=': 'dV', '%=': 'rM', - '&=': 'aN', - '|=': 'oR', - '^=': 'eO', + '&=': 'aN', 'and_eq': 'aN', + '|=': 'oR', 'or_eq': 'oR', + '^=': 'eO', 'xor_eq': 'eO', '<<': 'ls', '>>': 'rs', '<<=': 'lS', '>>=': 'rS', '==': 'eq', - '!=': 'ne', + '!=': 'ne', 'not_eq': 'ne', '<': 'lt', '>': 'gt', '<=': 'le', '>=': 'ge', - '!': 'nt', - '&&': 'aa', - '||': 'oo', + '!': 'nt', 'not': 'nt', + '&&': 'aa', 'and': 'aa', + '||': 'oo', 'or': 'oo', '++': 'pp', '--': 'mm', ',': 'cm', @@ -511,8 +512,8 @@ _id_operator_unary_v2 = { '&': 'ad', '+': 'ps', '-': 'ng', - '!': 'nt', - '~': 'co' + '!': 'nt', 'not': 'nt', + '~': 'co', 'compl': 'co' } _id_char_from_prefix = { None: 'c', 'u8': 'c', @@ -520,21 +521,21 @@ _id_char_from_prefix = { } # type: Dict[Any, str] # these are ordered by preceedence _expression_bin_ops = [ - ['||'], - ['&&'], - ['|'], - ['^'], - ['&'], - ['==', '!='], + ['||', 'or'], + ['&&', 'and'], + ['|', 'bitor'], + ['^', 'xor'], + ['&', 'bitand'], + ['==', '!=', 'not_eq'], ['<=', '>=', '<', '>'], ['<<', '>>'], ['+', '-'], ['*', '/', '%'], ['.*', '->*'] ] -_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"] +_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "not", "~", "compl"] _expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=", - ">>=", "<<=", "&=", "^=", "|="] + ">>=", "<<=", "&=", "and_eq", "^=", "|=", "xor_eq", "or_eq"] _id_explicit_cast = { 'dynamic_cast': 'dc', 'static_cast': 'sc', @@ -1260,7 +1261,10 @@ class ASTUnaryOpExpr(ASTExpression): self.expr = expr def _stringify(self, transform: StringifyTransform) -> str: - return transform(self.op) + transform(self.expr) + if self.op[0] in 'cn': + return transform(self.op) + " " + transform(self.expr) + else: + return transform(self.op) + transform(self.expr) def get_id(self, version: int) -> str: return _id_operator_unary_v2[self.op] + self.expr.get_id(version) @@ -1268,6 +1272,8 @@ class ASTUnaryOpExpr(ASTExpression): def describe_signature(self, signode: TextElement, mode: str, env: "BuildEnvironment", symbol: "Symbol") -> None: signode.append(nodes.Text(self.op)) + if self.op[0] in 'cn': + signode.append(nodes.Text(' ')) self.expr.describe_signature(signode, mode, env, symbol) @@ -1584,6 +1590,8 @@ class ASTOperatorBuildIn(ASTOperator): def get_id(self, version: int) -> str: if version == 1: ids = _id_operator_v1 + if self.op not in ids: + raise NoOldIdError() else: ids = _id_operator_v2 if self.op not in ids: @@ -1592,7 +1600,7 @@ class ASTOperatorBuildIn(ASTOperator): return ids[self.op] def _stringify(self, transform: StringifyTransform) -> str: - if self.op in ('new', 'new[]', 'delete', 'delete[]'): + if self.op in ('new', 'new[]', 'delete', 'delete[]') or self.op[0] in "abcnox": return 'operator ' + self.op else: return 'operator' + self.op @@ -5016,7 +5024,11 @@ class DefinitionParser(BaseParser): self.skip_ws() for op in _expression_unary_ops: # TODO: hmm, should we be able to backtrack here? - if self.skip_string(op): + if op[0] in 'cn': + res = self.skip_word(op) + else: + res = self.skip_string(op) + if res: expr = self._parse_cast_expression() return ASTUnaryOpExpr(op, expr) if self.skip_word_and_ws('sizeof'): @@ -5144,8 +5156,12 @@ class DefinitionParser(BaseParser): pos = self.pos oneMore = False for op in _expression_bin_ops[opId]: - if not self.skip_string(op): - continue + if op[0] in 'abcnox': + if not self.skip_word(op): + continue + else: + if not self.skip_string(op): + continue if op == '&' and self.current_char == '&': # don't split the && 'token' self.pos -= 1 @@ -5187,8 +5203,12 @@ class DefinitionParser(BaseParser): oneMore = False self.skip_ws() for op in _expression_assignment_ops: - if not self.skip_string(op): - continue + if op[0] in 'anox': + if not self.skip_word(op): + continue + else: + if not self.skip_string(op): + continue expr = self._parse_logical_or_expression(False) exprs.append(expr) ops.append(op) diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index aa8bb97b2..c6d0df8aa 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -197,7 +197,9 @@ def test_expressions(): exprCheck('+5', 'psL5E') exprCheck('-5', 'ngL5E') exprCheck('!5', 'ntL5E') + exprCheck('not 5', 'ntL5E') exprCheck('~5', 'coL5E') + exprCheck('compl 5', 'coL5E') exprCheck('sizeof...(a)', 'sZ1a') exprCheck('sizeof(T)', 'st1T') exprCheck('sizeof -42', 'szngL42E') @@ -221,13 +223,19 @@ def test_expressions(): exprCheck('(int)2', 'cviL2E') # binary op exprCheck('5 || 42', 'ooL5EL42E') + exprCheck('5 or 42', 'ooL5EL42E') exprCheck('5 && 42', 'aaL5EL42E') + exprCheck('5 and 42', 'aaL5EL42E') exprCheck('5 | 42', 'orL5EL42E') + exprCheck('5 bitor 42', 'orL5EL42E') exprCheck('5 ^ 42', 'eoL5EL42E') + exprCheck('5 xor 42', 'eoL5EL42E') exprCheck('5 & 42', 'anL5EL42E') + exprCheck('5 bitand 42', 'anL5EL42E') # ['==', '!='] exprCheck('5 == 42', 'eqL5EL42E') exprCheck('5 != 42', 'neL5EL42E') + exprCheck('5 not_eq 42', 'neL5EL42E') # ['<=', '>=', '<', '>'] exprCheck('5 <= 42', 'leL5EL42E') exprCheck('A <= 42', 'le1AL42E') @@ -261,8 +269,11 @@ def test_expressions(): exprCheck('a >>= 5', 'rS1aL5E') exprCheck('a <<= 5', 'lS1aL5E') exprCheck('a &= 5', 'aN1aL5E') + exprCheck('a and_eq 5', 'aN1aL5E') exprCheck('a ^= 5', 'eO1aL5E') + exprCheck('a xor_eq 5', 'eO1aL5E') exprCheck('a |= 5', 'oR1aL5E') + exprCheck('a or_eq 5', 'oR1aL5E') # Additional tests # a < expression that starts with something that could be a template @@ -531,30 +542,62 @@ def test_function_definitions(): def test_operators(): - check('function', 'void operator new [ ] ()', - {1: "new-array-operator", 2: "nav"}, output='void operator new[]()') - check('function', 'void operator delete ()', - {1: "delete-operator", 2: "dlv"}, output='void operator delete()') - check('function', 'operator bool() const', - {1: "castto-b-operatorC", 2: "NKcvbEv"}, output='operator bool() const') + check('function', 'void operator new()', {1: "new-operator", 2: "nwv"}) + check('function', 'void operator new[]()', {1: "new-array-operator", 2: "nav"}) + check('function', 'void operator delete()', {1: "delete-operator", 2: "dlv"}) + check('function', 'void operator delete[]()', {1: "delete-array-operator", 2: "dav"}) + check('function', 'operator bool() const', {1: "castto-b-operatorC", 2: "NKcvbEv"}) + check('function', 'void operator""_udl()', {2: 'li4_udlv'}) - check('function', 'void operator * ()', - {1: "mul-operator", 2: "mlv"}, output='void operator*()') - check('function', 'void operator - ()', - {1: "sub-operator", 2: "miv"}, output='void operator-()') - check('function', 'void operator + ()', - {1: "add-operator", 2: "plv"}, output='void operator+()') - check('function', 'void operator = ()', - {1: "assign-operator", 2: "aSv"}, output='void operator=()') - check('function', 'void operator / ()', - {1: "div-operator", 2: "dvv"}, output='void operator/()') - check('function', 'void operator % ()', - {1: "mod-operator", 2: "rmv"}, output='void operator%()') - check('function', 'void operator ! ()', - {1: "not-operator", 2: "ntv"}, output='void operator!()') - - check('function', 'void operator "" _udl()', - {2: 'li4_udlv'}, output='void operator""_udl()') + check('function', 'void operator~()', {1: "inv-operator", 2: "cov"}) + check('function', 'void operator compl()', {2: "cov"}) + check('function', 'void operator+()', {1: "add-operator", 2: "plv"}) + check('function', 'void operator-()', {1: "sub-operator", 2: "miv"}) + check('function', 'void operator*()', {1: "mul-operator", 2: "mlv"}) + check('function', 'void operator/()', {1: "div-operator", 2: "dvv"}) + check('function', 'void operator%()', {1: "mod-operator", 2: "rmv"}) + check('function', 'void operator&()', {1: "and-operator", 2: "anv"}) + check('function', 'void operator bitand()', {2: "anv"}) + check('function', 'void operator|()', {1: "or-operator", 2: "orv"}) + check('function', 'void operator bitor()', {2: "orv"}) + check('function', 'void operator^()', {1: "xor-operator", 2: "eov"}) + check('function', 'void operator xor()', {2: "eov"}) + check('function', 'void operator=()', {1: "assign-operator", 2: "aSv"}) + check('function', 'void operator+=()', {1: "add-assign-operator", 2: "pLv"}) + check('function', 'void operator-=()', {1: "sub-assign-operator", 2: "mIv"}) + check('function', 'void operator*=()', {1: "mul-assign-operator", 2: "mLv"}) + check('function', 'void operator/=()', {1: "div-assign-operator", 2: "dVv"}) + check('function', 'void operator%=()', {1: "mod-assign-operator", 2: "rMv"}) + check('function', 'void operator&=()', {1: "and-assign-operator", 2: "aNv"}) + check('function', 'void operator and_eq()', {2: "aNv"}) + check('function', 'void operator|=()', {1: "or-assign-operator", 2: "oRv"}) + check('function', 'void operator or_eq()', {2: "oRv"}) + check('function', 'void operator^=()', {1: "xor-assign-operator", 2: "eOv"}) + check('function', 'void operator xor_eq()', {2: "eOv"}) + check('function', 'void operator<<()', {1: "lshift-operator", 2: "lsv"}) + check('function', 'void operator>>()', {1: "rshift-operator", 2: "rsv"}) + check('function', 'void operator<<=()', {1: "lshift-assign-operator", 2: "lSv"}) + check('function', 'void operator>>=()', {1: "rshift-assign-operator", 2: "rSv"}) + check('function', 'void operator==()', {1: "eq-operator", 2: "eqv"}) + check('function', 'void operator!=()', {1: "neq-operator", 2: "nev"}) + check('function', 'void operator not_eq()', {2: "nev"}) + check('function', 'void operator<()', {1: "lt-operator", 2: "ltv"}) + check('function', 'void operator>()', {1: "gt-operator", 2: "gtv"}) + check('function', 'void operator<=()', {1: "lte-operator", 2: "lev"}) + check('function', 'void operator>=()', {1: "gte-operator", 2: "gev"}) + check('function', 'void operator!()', {1: "not-operator", 2: "ntv"}) + check('function', 'void operator not()', {2: "ntv"}) + check('function', 'void operator&&()', {1: "sand-operator", 2: "aav"}) + check('function', 'void operator and()', {2: "aav"}) + check('function', 'void operator||()', {1: "sor-operator", 2: "oov"}) + check('function', 'void operator or()', {2: "oov"}) + check('function', 'void operator++()', {1: "inc-operator", 2: "ppv"}) + check('function', 'void operator--()', {1: "dec-operator", 2: "mmv"}) + check('function', 'void operator,()', {1: "comma-operator", 2: "cmv"}) + check('function', 'void operator->*()', {1: "pointer-by-pointer-operator", 2: "pmv"}) + check('function', 'void operator->()', {1: "pointer-operator", 2: "ptv"}) + check('function', 'void operator()()', {1: "call-operator", 2: "clv"}) + check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"}) def test_class_definitions():