pax_global_header00006660000000000000000000000064145503504400014512gustar00rootroot0000000000000052 comment=7aac68c1025473bda98141e3ca5fdfde34c45429 cxxheaderparser-1.3.1/000077500000000000000000000000001455035044000147045ustar00rootroot00000000000000cxxheaderparser-1.3.1/.github/000077500000000000000000000000001455035044000162445ustar00rootroot00000000000000cxxheaderparser-1.3.1/.github/ISSUE_TEMPLATE/000077500000000000000000000000001455035044000204275ustar00rootroot00000000000000cxxheaderparser-1.3.1/.github/ISSUE_TEMPLATE/bug-report.yml000066400000000000000000000010371455035044000232410ustar00rootroot00000000000000name: Other bug Report description: File an issue about the Python API or other non-parsing issues title: "[BUG]: " body: - type: textarea id: description attributes: label: Problem description placeholder: >- Provide a short description, state the expected behavior and what actually happens. validations: required: true - type: textarea id: code attributes: label: Reproducible example code placeholder: >- Minimal code to reproduce this issue render: textcxxheaderparser-1.3.1/.github/ISSUE_TEMPLATE/parser-error.yml000066400000000000000000000011211455035044000235700ustar00rootroot00000000000000name: C++ parsing error description: cxxheaderparser fails to parse valid C/C++ code title: "[PARSE BUG]: " body: - type: textarea id: description attributes: label: Problem description placeholder: >- Provide a short description validations: required: true - type: textarea id: code attributes: label: C++ code that can't be parsed correctly (please double-check that https://robotpy.github.io/cxxheaderparser/ has the same error) placeholder: >- Paste header here render: text validations: required: truecxxheaderparser-1.3.1/.github/workflows/000077500000000000000000000000001455035044000203015ustar00rootroot00000000000000cxxheaderparser-1.3.1/.github/workflows/deploy.yml000066400000000000000000000013451455035044000223230ustar00rootroot00000000000000# deploy to github pages name: Build and Deploy on: push: branches: - main jobs: deploy: concurrency: ci-${{ github.ref }} runs-on: ubuntu-latest steps: - name: Checkout ๐Ÿ›Ž๏ธ uses: actions/checkout@v3 with: fetch-depth: 0 - name: Build run: | echo "__version__ = '$(git describe --tags)'" > cxxheaderparser/version.py mkdir build cp -r cxxheaderparser build - name: Deploy ๐Ÿš€ uses: JamesIves/github-pages-deploy-action@v4.3.3 with: branch: gh-pages folder: build clean: true clean-exclude: | .nojekyll index.html _index.pycxxheaderparser-1.3.1/.github/workflows/dist.yml000066400000000000000000000061331455035044000217720ustar00rootroot00000000000000--- name: dist on: pull_request: push: branches: - main tags: - '*' concurrency: group: ${{ github.ref }} cancel-in-progress: true jobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: psf/black@stable check-mypy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 # - uses: jpetrucciani/mypy-check@0.930 # .. can't use that because we need to install pytest - uses: actions/setup-python@v4 with: python-version: 3.8 - name: Install requirements run: | pip --disable-pip-version-check install mypy pytest pcpp - name: Run mypy run: | mypy . check-doc: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: 3.8 - name: Sphinx run: | pip --disable-pip-version-check install -e . pip --disable-pip-version-check install -r docs/requirements.txt cd docs && make clean html SPHINXOPTS="-W --keep-going" # # Build a wheel # build: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: 3.8 - run: pipx run build - name: Upload build artifacts uses: actions/upload-artifact@v3 with: name: dist path: dist test: needs: [build] runs-on: ${{ matrix.os }} strategy: matrix: os: [windows-latest, macos-latest, ubuntu-20.04] python_version: [3.6, 3.7, 3.8, 3.9, "3.10", "3.11", "3.12"] architecture: [x86, x64] exclude: - os: macos-latest architecture: x86 - os: ubuntu-20.04 architecture: x86 steps: - uses: actions/checkout@v3 with: submodules: recursive fetch-depth: 0 - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python_version }} architecture: ${{ matrix.architecture }} - name: Download build artifacts uses: actions/download-artifact@v3 with: name: dist path: dist - name: Install test dependencies run: python -m pip --disable-pip-version-check install -r tests/requirements.txt - name: Setup MSVC compiler uses: ilammy/msvc-dev-cmd@v1 if: matrix.os == 'windows-latest' - name: Test wheel shell: bash run: | cd dist python -m pip --disable-pip-version-check install *.whl cd ../tests python -m pytest publish: runs-on: ubuntu-latest needs: [check, check-mypy, check-doc, test] permissions: id-token: write if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') steps: - name: Download build artifacts uses: actions/download-artifact@v3 with: name: dist path: dist - name: Publish to PyPI uses: pypa/gh-action-pypi-publish@release/v1 cxxheaderparser-1.3.1/.gitignore000066400000000000000000000001371455035044000166750ustar00rootroot00000000000000*.py[cod] *.egg-info /build /dist /cxxheaderparser/version.py /.vscode .coverage .pytest_cachecxxheaderparser-1.3.1/.readthedocs.yml000066400000000000000000000003011455035044000177640ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py build: os: ubuntu-22.04 tools: python: "3.11" python: install: - requirements: docs/requirements.txt - method: pip path: . cxxheaderparser-1.3.1/LICENSE.txt000066400000000000000000000146321455035044000165350ustar00rootroot00000000000000cxxheaderparser license: Copyright (c) 2020-2022 Dustin Spicuzza All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- CppHeaderParser license: Copyright (C) 2011, Jashua R. Cloutier All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Jashua R. Cloutier nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. Stories, blog entries etc making reference to this project may mention the name Jashua R. Cloutier in terms of project originator/creator etc. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- PLY license: Copyright (C) 2001-2020 David M. Beazley (Dabeaz LLC) All rights reserved. Latest version: https://github.com/dabeaz/ply Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of David Beazley or Dabeaz LLC may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------- pycparser -- A C parser in Python Copyright (c) 2008-2022, Eli Bendersky All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cxxheaderparser-1.3.1/README.md000066400000000000000000000070761455035044000161750ustar00rootroot00000000000000cxxheaderparser =============== A pure python C++ header parser that parses C++ headers in a mildly naive manner that allows it to handle many C++ constructs, including many modern (C++11 and beyond) features. This is a complete rewrite of the `CppHeaderParser` library. `CppHeaderParser` is really useful for some tasks, but it's implementation is a truly terrible ugly hack built on top of other terrible hacks. This rewrite tries to learn from `CppHeaderParser` and leave its ugly baggage behind. Goals: * Parse syntatically valid C++ and provide a useful (and documented!) pure python API to work with the parsed data * Process incomplete headers (doesn't need to process includes) * Provide enough information for binding generators to wrap C++ code * Handle common C++ features, but it may struggle with obscure or overly complex things (feel free to make a PR to fix it!) Non-goals: * **Does not produce a full AST**, use Clang if you need that * **Not intended to validate C++**, which means this will not reject all invalid C++ headers! Use a compiler if you need that * **No C preprocessor substitution support implemented**. If you are parsing headers that contain macros, you should preprocess your code using the excellent pure python preprocessor [pcpp](https://github.com/ned14/pcpp) or your favorite compiler * See `cxxheaderparser.preprocessor` for how to use * Probably won't be able to parse most IOCCC entries There are two APIs available: * A visitor-style interface to build up your own custom data structures * A simple visitor that stores everything in a giant data structure Live Demo --------- A pyodide-powered interactive demo is at https://robotpy.github.io/cxxheaderparser/ Documentation ------------- Documentation can be found at https://cxxheaderparser.readthedocs.io Install ------- Requires Python 3.6+, no non-stdlib dependencies if using Python 3.7+. ``` pip install cxxheaderparser ``` Usage ----- To see a dump of the data parsed from a header: ``` # pprint format python -m cxxheaderparser myheader.h # JSON format python -m cxxheaderparser --mode=json myheader.h # dataclasses repr format python -m cxxheaderparser --mode=repr myheader.h # dataclasses repr format (formatted with black) python -m cxxheaderparser --mode=brepr myheader.h ``` See the documentation for anything more complex. Bugs ---- This should handle even complex C++ code with few problems, but there are almost certainly weird edge cases that it doesn't handle. Additionally, not all C++17/20 constructs are supported yet (but contributions welcome!). If you find an bug, we encourage you to submit a pull request! New changes will only be accepted if there are tests to cover the change you made (and if they donโ€™t break existing tests). Author ------ cxxheaderparser was created by Dustin Spicuzza Credit ------ * Partially derived from and inspired by the `CppHeaderParser` project originally developed by Jashua Cloutier * An embedded version of PLY is used for lexing tokens * Portions of the lexer grammar and other ideas were derived from pycparser * The source code is liberally sprinkled with comments containing C++ parsing grammar mostly derived from the [Hyperlinked C++ BNF Grammar](https://www.nongnu.org/hcb/) * cppreference.com has been invaluable for understanding many of the weird quirks of C++, and some of the unit tests use examples from there * [Compiler Explorer](godbolt.org) has been invaluable for validating my understanding of C++ by allowing me to quickly type in quirky C++ constructs to see if they actually compile License ------- BSD License cxxheaderparser-1.3.1/cxxheaderparser/000077500000000000000000000000001455035044000200745ustar00rootroot00000000000000cxxheaderparser-1.3.1/cxxheaderparser/__init__.py000066400000000000000000000001511455035044000222020ustar00rootroot00000000000000try: from .version import __version__ # type: ignore except ImportError: __version__ = "master" cxxheaderparser-1.3.1/cxxheaderparser/__main__.py000066400000000000000000000001251455035044000221640ustar00rootroot00000000000000from cxxheaderparser.dump import dumpmain if __name__ == "__main__": dumpmain() cxxheaderparser-1.3.1/cxxheaderparser/_ply/000077500000000000000000000000001455035044000210375ustar00rootroot00000000000000cxxheaderparser-1.3.1/cxxheaderparser/_ply/__init__.py000066400000000000000000000001641455035044000231510ustar00rootroot00000000000000# PLY package # Author: David Beazley (dave@dabeaz.com) # https://github.com/dabeaz/ply __version__ = "2022.10.27" cxxheaderparser-1.3.1/cxxheaderparser/_ply/lex.py000066400000000000000000001046641455035044000222140ustar00rootroot00000000000000# fmt: off # ----------------------------------------------------------------------------- # ply: lex.py # # Copyright (C) 2001-2022 # David M. Beazley (Dabeaz LLC) # All rights reserved. # # Latest version: https://github.com/dabeaz/ply # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of David Beazley or Dabeaz LLC may be used to # endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- import re import sys import types import copy import os import inspect # This tuple contains acceptable string types StringTypes = (str, bytes) # This regular expression is used to match valid token names _is_identifier = re.compile(r'^[a-zA-Z0-9_]+$') # Exception thrown when invalid token encountered and no default error # handler is defined. class LexError(Exception): def __init__(self, message, s): self.args = (message,) self.text = s # Token class. This class is used to represent the tokens produced. class LexToken(object): def __repr__(self): return f'LexToken({self.type},{self.value!r},{self.lineno},{self.lexpos})' # This object is a stand-in for a logging object created by the # logging module. class PlyLogger(object): def __init__(self, f): self.f = f def critical(self, msg, *args, **kwargs): self.f.write((msg % args) + '\n') def warning(self, msg, *args, **kwargs): self.f.write('WARNING: ' + (msg % args) + '\n') def error(self, msg, *args, **kwargs): self.f.write('ERROR: ' + (msg % args) + '\n') info = critical debug = critical # ----------------------------------------------------------------------------- # === Lexing Engine === # # The following Lexer class implements the lexer runtime. There are only # a few public methods and attributes: # # input() - Store a new string in the lexer # token() - Get the next token # clone() - Clone the lexer # # lineno - Current line number # lexpos - Current position in the input string # ----------------------------------------------------------------------------- class Lexer: def __init__(self): self.lexre = None # Master regular expression. This is a list of # tuples (re, findex) where re is a compiled # regular expression and findex is a list # mapping regex group numbers to rules self.lexretext = None # Current regular expression strings self.lexstatere = {} # Dictionary mapping lexer states to master regexs self.lexstateretext = {} # Dictionary mapping lexer states to regex strings self.lexstaterenames = {} # Dictionary mapping lexer states to symbol names self.lexstate = 'INITIAL' # Current lexer state self.lexstatestack = [] # Stack of lexer states self.lexstateinfo = None # State information self.lexstateignore = {} # Dictionary of ignored characters for each state self.lexstateerrorf = {} # Dictionary of error functions for each state self.lexstateeoff = {} # Dictionary of eof functions for each state self.lexreflags = 0 # Optional re compile flags self.lexdata = None # Actual input data (as a string) self.lexpos = 0 # Current position in input text self.lexlen = 0 # Length of the input text self.lexerrorf = None # Error rule (if any) self.lexeoff = None # EOF rule (if any) self.lextokens = None # List of valid tokens self.lexignore = '' # Ignored characters self.lexliterals = '' # Literal characters that can be passed through self.lexmodule = None # Module self.lineno = 1 # Current line number def clone(self, object=None): c = copy.copy(self) # If the object parameter has been supplied, it means we are attaching the # lexer to a new object. In this case, we have to rebind all methods in # the lexstatere and lexstateerrorf tables. if object: newtab = {} for key, ritem in self.lexstatere.items(): newre = [] for cre, findex in ritem: newfindex = [] for f in findex: if not f or not f[0]: newfindex.append(f) continue newfindex.append((getattr(object, f[0].__name__), f[1])) newre.append((cre, newfindex)) newtab[key] = newre c.lexstatere = newtab c.lexstateerrorf = {} for key, ef in self.lexstateerrorf.items(): c.lexstateerrorf[key] = getattr(object, ef.__name__) c.lexmodule = object return c # ------------------------------------------------------------ # input() - Push a new string into the lexer # ------------------------------------------------------------ def input(self, s): self.lexdata = s self.lexpos = 0 self.lexlen = len(s) # ------------------------------------------------------------ # begin() - Changes the lexing state # ------------------------------------------------------------ def begin(self, state): if state not in self.lexstatere: raise ValueError(f'Undefined state {state!r}') self.lexre = self.lexstatere[state] self.lexretext = self.lexstateretext[state] self.lexignore = self.lexstateignore.get(state, '') self.lexerrorf = self.lexstateerrorf.get(state, None) self.lexeoff = self.lexstateeoff.get(state, None) self.lexstate = state # ------------------------------------------------------------ # push_state() - Changes the lexing state and saves old on stack # ------------------------------------------------------------ def push_state(self, state): self.lexstatestack.append(self.lexstate) self.begin(state) # ------------------------------------------------------------ # pop_state() - Restores the previous state # ------------------------------------------------------------ def pop_state(self): self.begin(self.lexstatestack.pop()) # ------------------------------------------------------------ # current_state() - Returns the current lexing state # ------------------------------------------------------------ def current_state(self): return self.lexstate # ------------------------------------------------------------ # skip() - Skip ahead n characters # ------------------------------------------------------------ def skip(self, n): self.lexpos += n # ------------------------------------------------------------ # token() - Return the next token from the Lexer # # Note: This function has been carefully implemented to be as fast # as possible. Don't make changes unless you really know what # you are doing # ------------------------------------------------------------ def token(self): # Make local copies of frequently referenced attributes lexpos = self.lexpos lexlen = self.lexlen lexignore = self.lexignore lexdata = self.lexdata while lexpos < lexlen: # This code provides some short-circuit code for whitespace, tabs, and other ignored characters if lexdata[lexpos] in lexignore: lexpos += 1 continue # Look for a regular expression match for lexre, lexindexfunc in self.lexre: m = lexre.match(lexdata, lexpos) if not m: continue # Create a token for return tok = LexToken() tok.value = m.group() tok.lineno = self.lineno tok.lexpos = lexpos i = m.lastindex func, tok.type = lexindexfunc[i] if not func: # If no token type was set, it's an ignored token if tok.type: self.lexpos = m.end() return tok else: lexpos = m.end() break lexpos = m.end() # If token is processed by a function, call it tok.lexer = self # Set additional attributes useful in token rules self.lexmatch = m self.lexpos = lexpos newtok = func(tok) del tok.lexer del self.lexmatch # Every function must return a token, if nothing, we just move to next token if not newtok: lexpos = self.lexpos # This is here in case user has updated lexpos. lexignore = self.lexignore # This is here in case there was a state change break return newtok else: # No match, see if in literals if lexdata[lexpos] in self.lexliterals: tok = LexToken() tok.value = lexdata[lexpos] tok.lineno = self.lineno tok.type = tok.value tok.lexpos = lexpos self.lexpos = lexpos + 1 return tok # No match. Call t_error() if defined. if self.lexerrorf: tok = LexToken() tok.value = self.lexdata[lexpos:] tok.lineno = self.lineno tok.type = 'error' tok.lexer = self tok.lexpos = lexpos self.lexpos = lexpos newtok = self.lexerrorf(tok) if lexpos == self.lexpos: # Error method didn't change text position at all. This is an error. raise LexError(f"Scanning error. Illegal character {lexdata[lexpos]!r}", lexdata[lexpos:]) lexpos = self.lexpos if not newtok: continue return newtok self.lexpos = lexpos raise LexError(f"Illegal character {lexdata[lexpos]!r} at index {lexpos}", lexdata[lexpos:]) if self.lexeoff: tok = LexToken() tok.type = 'eof' tok.value = '' tok.lineno = self.lineno tok.lexpos = lexpos tok.lexer = self self.lexpos = lexpos newtok = self.lexeoff(tok) return newtok self.lexpos = lexpos + 1 if self.lexdata is None: raise RuntimeError('No input string given with input()') return None # Iterator interface def __iter__(self): return self def __next__(self): t = self.token() if t is None: raise StopIteration return t # ----------------------------------------------------------------------------- # ==== Lex Builder === # # The functions and classes below are used to collect lexing information # and build a Lexer object from it. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- # _get_regex(func) # # Returns the regular expression assigned to a function either as a doc string # or as a .regex attribute attached by the @TOKEN decorator. # ----------------------------------------------------------------------------- def _get_regex(func): return getattr(func, 'regex', func.__doc__) # ----------------------------------------------------------------------------- # get_caller_module_dict() # # This function returns a dictionary containing all of the symbols defined within # a caller further down the call stack. This is used to get the environment # associated with the yacc() call if none was provided. # ----------------------------------------------------------------------------- def get_caller_module_dict(levels): f = sys._getframe(levels) return { **f.f_globals, **f.f_locals } # ----------------------------------------------------------------------------- # _form_master_re() # # This function takes a list of all of the regex components and attempts to # form the master regular expression. Given limitations in the Python re # module, it may be necessary to break the master regex into separate expressions. # ----------------------------------------------------------------------------- def _form_master_re(relist, reflags, ldict, toknames): if not relist: return [], [], [] regex = '|'.join(relist) try: lexre = re.compile(regex, reflags) # Build the index to function map for the matching engine lexindexfunc = [None] * (max(lexre.groupindex.values()) + 1) lexindexnames = lexindexfunc[:] for f, i in lexre.groupindex.items(): handle = ldict.get(f, None) if type(handle) in (types.FunctionType, types.MethodType): lexindexfunc[i] = (handle, toknames[f]) lexindexnames[i] = f elif handle is not None: lexindexnames[i] = f if f.find('ignore_') > 0: lexindexfunc[i] = (None, None) else: lexindexfunc[i] = (None, toknames[f]) return [(lexre, lexindexfunc)], [regex], [lexindexnames] except Exception: m = (len(relist) // 2) + 1 llist, lre, lnames = _form_master_re(relist[:m], reflags, ldict, toknames) rlist, rre, rnames = _form_master_re(relist[m:], reflags, ldict, toknames) return (llist+rlist), (lre+rre), (lnames+rnames) # ----------------------------------------------------------------------------- # def _statetoken(s,names) # # Given a declaration name s of the form "t_" and a dictionary whose keys are # state names, this function returns a tuple (states,tokenname) where states # is a tuple of state names and tokenname is the name of the token. For example, # calling this with s = "t_foo_bar_SPAM" might return (('foo','bar'),'SPAM') # ----------------------------------------------------------------------------- def _statetoken(s, names): parts = s.split('_') for i, part in enumerate(parts[1:], 1): if part not in names and part != 'ANY': break if i > 1: states = tuple(parts[1:i]) else: states = ('INITIAL',) if 'ANY' in states: states = tuple(names) tokenname = '_'.join(parts[i:]) return (states, tokenname) # ----------------------------------------------------------------------------- # LexerReflect() # # This class represents information needed to build a lexer as extracted from a # user's input file. # ----------------------------------------------------------------------------- class LexerReflect(object): def __init__(self, ldict, log=None, reflags=0): self.ldict = ldict self.error_func = None self.tokens = [] self.reflags = reflags self.stateinfo = {'INITIAL': 'inclusive'} self.modules = set() self.error = False self.log = PlyLogger(sys.stderr) if log is None else log # Get all of the basic information def get_all(self): self.get_tokens() self.get_literals() self.get_states() self.get_rules() # Validate all of the information def validate_all(self): self.validate_tokens() self.validate_literals() self.validate_rules() return self.error # Get the tokens map def get_tokens(self): tokens = self.ldict.get('tokens', None) if not tokens: self.log.error('No token list is defined') self.error = True return if not isinstance(tokens, (list, tuple)): self.log.error('tokens must be a list or tuple') self.error = True return if not tokens: self.log.error('tokens is empty') self.error = True return self.tokens = tokens # Validate the tokens def validate_tokens(self): terminals = {} for n in self.tokens: if not _is_identifier.match(n): self.log.error(f"Bad token name {n!r}") self.error = True if n in terminals: self.log.warning(f"Token {n!r} multiply defined") terminals[n] = 1 # Get the literals specifier def get_literals(self): self.literals = self.ldict.get('literals', '') if not self.literals: self.literals = '' # Validate literals def validate_literals(self): try: for c in self.literals: if not isinstance(c, StringTypes) or len(c) > 1: self.log.error(f'Invalid literal {c!r}. Must be a single character') self.error = True except TypeError: self.log.error('Invalid literals specification. literals must be a sequence of characters') self.error = True def get_states(self): self.states = self.ldict.get('states', None) # Build statemap if self.states: if not isinstance(self.states, (tuple, list)): self.log.error('states must be defined as a tuple or list') self.error = True else: for s in self.states: if not isinstance(s, tuple) or len(s) != 2: self.log.error("Invalid state specifier %r. Must be a tuple (statename,'exclusive|inclusive')", s) self.error = True continue name, statetype = s if not isinstance(name, StringTypes): self.log.error('State name %r must be a string', name) self.error = True continue if not (statetype == 'inclusive' or statetype == 'exclusive'): self.log.error("State type for state %r must be 'inclusive' or 'exclusive'", name) self.error = True continue if name in self.stateinfo: self.log.error("State %r already defined", name) self.error = True continue self.stateinfo[name] = statetype # Get all of the symbols with a t_ prefix and sort them into various # categories (functions, strings, error functions, and ignore characters) def get_rules(self): tsymbols = [f for f in self.ldict if f[:2] == 't_'] # Now build up a list of functions and a list of strings self.toknames = {} # Mapping of symbols to token names self.funcsym = {} # Symbols defined as functions self.strsym = {} # Symbols defined as strings self.ignore = {} # Ignore strings by state self.errorf = {} # Error functions by state self.eoff = {} # EOF functions by state for s in self.stateinfo: self.funcsym[s] = [] self.strsym[s] = [] if len(tsymbols) == 0: self.log.error('No rules of the form t_rulename are defined') self.error = True return for f in tsymbols: t = self.ldict[f] states, tokname = _statetoken(f, self.stateinfo) self.toknames[f] = tokname if hasattr(t, '__call__'): if tokname == 'error': for s in states: self.errorf[s] = t elif tokname == 'eof': for s in states: self.eoff[s] = t elif tokname == 'ignore': line = t.__code__.co_firstlineno file = t.__code__.co_filename self.log.error("%s:%d: Rule %r must be defined as a string", file, line, t.__name__) self.error = True else: for s in states: self.funcsym[s].append((f, t)) elif isinstance(t, StringTypes): if tokname == 'ignore': for s in states: self.ignore[s] = t if '\\' in t: self.log.warning("%s contains a literal backslash '\\'", f) elif tokname == 'error': self.log.error("Rule %r must be defined as a function", f) self.error = True else: for s in states: self.strsym[s].append((f, t)) else: self.log.error('%s not defined as a function or string', f) self.error = True # Sort the functions by line number for f in self.funcsym.values(): f.sort(key=lambda x: x[1].__code__.co_firstlineno) # Sort the strings by regular expression length for s in self.strsym.values(): s.sort(key=lambda x: len(x[1]), reverse=True) # Validate all of the t_rules collected def validate_rules(self): for state in self.stateinfo: # Validate all rules defined by functions for fname, f in self.funcsym[state]: line = f.__code__.co_firstlineno file = f.__code__.co_filename module = inspect.getmodule(f) self.modules.add(module) tokname = self.toknames[fname] if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = f.__code__.co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) self.error = True continue if nargs < reqargs: self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) self.error = True continue if not _get_regex(f): self.log.error("%s:%d: No regular expression defined for rule %r", file, line, f.__name__) self.error = True continue try: c = re.compile('(?P<%s>%s)' % (fname, _get_regex(f)), self.reflags) if c.match(''): self.log.error("%s:%d: Regular expression for rule %r matches empty string", file, line, f.__name__) self.error = True except re.error as e: self.log.error("%s:%d: Invalid regular expression for rule '%s'. %s", file, line, f.__name__, e) if '#' in _get_regex(f): self.log.error("%s:%d. Make sure '#' in rule %r is escaped with '\\#'", file, line, f.__name__) self.error = True # Validate all rules defined by strings for name, r in self.strsym[state]: tokname = self.toknames[name] if tokname == 'error': self.log.error("Rule %r must be defined as a function", name) self.error = True continue if tokname not in self.tokens and tokname.find('ignore_') < 0: self.log.error("Rule %r defined for an unspecified token %s", name, tokname) self.error = True continue try: c = re.compile('(?P<%s>%s)' % (name, r), self.reflags) if (c.match('')): self.log.error("Regular expression for rule %r matches empty string", name) self.error = True except re.error as e: self.log.error("Invalid regular expression for rule %r. %s", name, e) if '#' in r: self.log.error("Make sure '#' in rule %r is escaped with '\\#'", name) self.error = True if not self.funcsym[state] and not self.strsym[state]: self.log.error("No rules defined for state %r", state) self.error = True # Validate the error function efunc = self.errorf.get(state, None) if efunc: f = efunc line = f.__code__.co_firstlineno file = f.__code__.co_filename module = inspect.getmodule(f) self.modules.add(module) if isinstance(f, types.MethodType): reqargs = 2 else: reqargs = 1 nargs = f.__code__.co_argcount if nargs > reqargs: self.log.error("%s:%d: Rule %r has too many arguments", file, line, f.__name__) self.error = True if nargs < reqargs: self.log.error("%s:%d: Rule %r requires an argument", file, line, f.__name__) self.error = True for module in self.modules: self.validate_module(module) # ----------------------------------------------------------------------------- # validate_module() # # This checks to see if there are duplicated t_rulename() functions or strings # in the parser input file. This is done using a simple regular expression # match on each line in the source code of the given module. # ----------------------------------------------------------------------------- def validate_module(self, module): try: lines, linen = inspect.getsourcelines(module) except IOError: return fre = re.compile(r'\s*def\s+(t_[a-zA-Z_0-9]*)\(') sre = re.compile(r'\s*(t_[a-zA-Z_0-9]*)\s*=') counthash = {} linen += 1 for line in lines: m = fre.match(line) if not m: m = sre.match(line) if m: name = m.group(1) prev = counthash.get(name) if not prev: counthash[name] = linen else: filename = inspect.getsourcefile(module) self.log.error('%s:%d: Rule %s redefined. Previously defined on line %d', filename, linen, name, prev) self.error = True linen += 1 # ----------------------------------------------------------------------------- # lex(module) # # Build all of the regular expression rules from definitions in the supplied module # ----------------------------------------------------------------------------- def lex(*, module=None, object=None, debug=False, reflags=int(re.VERBOSE), debuglog=None, errorlog=None): global lexer ldict = None stateinfo = {'INITIAL': 'inclusive'} lexobj = Lexer() global token, input if errorlog is None: errorlog = PlyLogger(sys.stderr) if debug: if debuglog is None: debuglog = PlyLogger(sys.stderr) # Get the module dictionary used for the lexer if object: module = object # Get the module dictionary used for the parser if module: _items = [(k, getattr(module, k)) for k in dir(module)] ldict = dict(_items) # If no __file__ attribute is available, try to obtain it from the __module__ instead if '__file__' not in ldict: ldict['__file__'] = sys.modules[ldict['__module__']].__file__ else: ldict = get_caller_module_dict(2) # Collect parser information from the dictionary linfo = LexerReflect(ldict, log=errorlog, reflags=reflags) linfo.get_all() if linfo.validate_all(): raise SyntaxError("Can't build lexer") # Dump some basic debugging information if debug: debuglog.info('lex: tokens = %r', linfo.tokens) debuglog.info('lex: literals = %r', linfo.literals) debuglog.info('lex: states = %r', linfo.stateinfo) # Build a dictionary of valid token names lexobj.lextokens = set() for n in linfo.tokens: lexobj.lextokens.add(n) # Get literals specification if isinstance(linfo.literals, (list, tuple)): lexobj.lexliterals = type(linfo.literals[0])().join(linfo.literals) else: lexobj.lexliterals = linfo.literals lexobj.lextokens_all = lexobj.lextokens | set(lexobj.lexliterals) # Get the stateinfo dictionary stateinfo = linfo.stateinfo regexs = {} # Build the master regular expressions for state in stateinfo: regex_list = [] # Add rules defined by functions first for fname, f in linfo.funcsym[state]: regex_list.append('(?P<%s>%s)' % (fname, _get_regex(f))) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", fname, _get_regex(f), state) # Now add all of the simple rules for name, r in linfo.strsym[state]: regex_list.append('(?P<%s>%s)' % (name, r)) if debug: debuglog.info("lex: Adding rule %s -> '%s' (state '%s')", name, r, state) regexs[state] = regex_list # Build the master regular expressions if debug: debuglog.info('lex: ==== MASTER REGEXS FOLLOW ====') for state in regexs: lexre, re_text, re_names = _form_master_re(regexs[state], reflags, ldict, linfo.toknames) lexobj.lexstatere[state] = lexre lexobj.lexstateretext[state] = re_text lexobj.lexstaterenames[state] = re_names if debug: for i, text in enumerate(re_text): debuglog.info("lex: state '%s' : regex[%d] = '%s'", state, i, text) # For inclusive states, we need to add the regular expressions from the INITIAL state for state, stype in stateinfo.items(): if state != 'INITIAL' and stype == 'inclusive': lexobj.lexstatere[state].extend(lexobj.lexstatere['INITIAL']) lexobj.lexstateretext[state].extend(lexobj.lexstateretext['INITIAL']) lexobj.lexstaterenames[state].extend(lexobj.lexstaterenames['INITIAL']) lexobj.lexstateinfo = stateinfo lexobj.lexre = lexobj.lexstatere['INITIAL'] lexobj.lexretext = lexobj.lexstateretext['INITIAL'] lexobj.lexreflags = reflags # Set up ignore variables lexobj.lexstateignore = linfo.ignore lexobj.lexignore = lexobj.lexstateignore.get('INITIAL', '') # Set up error functions lexobj.lexstateerrorf = linfo.errorf lexobj.lexerrorf = linfo.errorf.get('INITIAL', None) if not lexobj.lexerrorf: errorlog.warning('No t_error rule is defined') # Set up eof functions lexobj.lexstateeoff = linfo.eoff lexobj.lexeoff = linfo.eoff.get('INITIAL', None) # Check state information for ignore and error rules for s, stype in stateinfo.items(): if stype == 'exclusive': if s not in linfo.errorf: errorlog.warning("No error rule is defined for exclusive state %r", s) if s not in linfo.ignore and lexobj.lexignore: errorlog.warning("No ignore rule is defined for exclusive state %r", s) elif stype == 'inclusive': if s not in linfo.errorf: linfo.errorf[s] = linfo.errorf.get('INITIAL', None) if s not in linfo.ignore: linfo.ignore[s] = linfo.ignore.get('INITIAL', '') # Create global versions of the token() and input() functions token = lexobj.token input = lexobj.input lexer = lexobj return lexobj # ----------------------------------------------------------------------------- # runmain() # # This runs the lexer as a main program # ----------------------------------------------------------------------------- def runmain(lexer=None, data=None): if not data: try: filename = sys.argv[1] with open(filename) as f: data = f.read() except IndexError: sys.stdout.write('Reading from standard input (type EOF to end):\n') data = sys.stdin.read() if lexer: _input = lexer.input else: _input = input _input(data) if lexer: _token = lexer.token else: _token = token while True: tok = _token() if not tok: break sys.stdout.write(f'({tok.type},{tok.value!r},{tok.lineno},{tok.lexpos})\n') # ----------------------------------------------------------------------------- # @TOKEN(regex) # # This decorator function can be used to set the regex expression on a function # when its docstring might need to be set in an alternative way # ----------------------------------------------------------------------------- def TOKEN(r): def set_regex(f): if hasattr(r, '__call__'): f.regex = _get_regex(r) else: f.regex = r return f return set_regex cxxheaderparser-1.3.1/cxxheaderparser/dump.py000066400000000000000000000037521455035044000214220ustar00rootroot00000000000000import argparse import dataclasses import json import pprint import subprocess import sys from .options import ParserOptions from .simple import parse_file def dumpmain() -> None: parser = argparse.ArgumentParser() parser.add_argument("header") parser.add_argument( "-w", "--width", default=80, type=int, help="Width of output when in pprint mode", ) parser.add_argument("-v", "--verbose", default=False, action="store_true") parser.add_argument( "--mode", choices=["json", "pprint", "repr", "brepr", "pponly"], default="pprint", ) parser.add_argument( "--pcpp", default=False, action="store_true", help="Use pcpp preprocessor" ) parser.add_argument( "--encoding", default=None, help="Use this encoding to open the file" ) args = parser.parse_args() preprocessor = None if args.pcpp or args.mode == "pponly": from .preprocessor import make_pcpp_preprocessor preprocessor = make_pcpp_preprocessor(encoding=args.encoding) if args.mode == "pponly": with open(args.header, "r", encoding=args.encoding) as fp: pp_content = preprocessor(args.header, fp.read()) sys.stdout.write(pp_content) sys.exit(0) options = ParserOptions(verbose=args.verbose, preprocessor=preprocessor) data = parse_file(args.header, encoding=args.encoding, options=options) if args.mode == "pprint": ddata = dataclasses.asdict(data) pprint.pprint(ddata, width=args.width, compact=True) elif args.mode == "json": ddata = dataclasses.asdict(data) json.dump(ddata, sys.stdout, indent=2) elif args.mode == "brepr": stmt = repr(data) stmt = subprocess.check_output( ["black", "-", "-q"], input=stmt.encode("utf-8") ).decode("utf-8") print(stmt) elif args.mode == "repr": print(data) else: parser.error("Invalid mode") cxxheaderparser-1.3.1/cxxheaderparser/errors.py000066400000000000000000000004741455035044000217670ustar00rootroot00000000000000import typing if typing.TYPE_CHECKING: from .lexer import LexToken class CxxParseError(Exception): """ Exception raised when a parsing error occurs """ def __init__(self, msg: str, tok: typing.Optional["LexToken"] = None) -> None: Exception.__init__(self, msg) self.tok = tok cxxheaderparser-1.3.1/cxxheaderparser/gentest.py000066400000000000000000000077161455035044000221320ustar00rootroot00000000000000import argparse import dataclasses import inspect import subprocess import typing from .errors import CxxParseError from .preprocessor import make_pcpp_preprocessor from .options import ParserOptions from .simple import parse_string, ParsedData def nondefault_repr(data: ParsedData) -> str: """ Similar to the default dataclass repr, but exclude any default parameters or parameters with compare=False """ is_dataclass = dataclasses.is_dataclass get_fields = dataclasses.fields MISSING = dataclasses.MISSING def _inner_repr(o: typing.Any) -> str: if is_dataclass(o): vals = [] for f in get_fields(o): if f.repr and f.compare: v = getattr(o, f.name) if f.default_factory is not MISSING: default = f.default_factory() else: default = f.default if v != default: vals.append(f"{f.name}={_inner_repr(v)}") return f"{o.__class__.__qualname__ }({', '.join(vals)})" elif isinstance(o, list): return f"[{','.join(_inner_repr(l) for l in o)}]" elif isinstance(o, dict): vals = [] for k, v in o.items(): vals.append(f'"{k}": {_inner_repr(v)}') return "{" + ",".join(vals) + "}" else: return repr(o) return _inner_repr(data) def gentest( infile: str, name: str, outfile: str, verbose: bool, fail: bool, pcpp: bool ) -> None: # Goal is to allow making a unit test as easy as running this dumper # on a file and copy/pasting this into a test with open(infile, "r") as fp: content = fp.read() maybe_options = "" popt = "" options = ParserOptions(verbose=verbose) if pcpp: options.preprocessor = make_pcpp_preprocessor() maybe_options = "options = ParserOptions(preprocessor=make_pcpp_preprocessor())" popt = ", options=options" try: data = parse_string(content, options=options) if fail: raise ValueError("did not fail") except CxxParseError: if not fail: raise # do it again, but strip the content so the error message matches try: parse_string(content.strip(), options=options) except CxxParseError as e2: err = str(e2) if not fail: stmt = nondefault_repr(data) stmt = f""" {maybe_options} data = parse_string(content, cleandoc=True{popt}) assert data == {stmt} """ else: stmt = f""" {maybe_options} err = {repr(err)} with pytest.raises(CxxParseError, match=re.escape(err)): parse_string(content, cleandoc=True{popt}) """ content = ("\n" + content.strip()).replace("\n", "\n ") content = "\n".join(l.rstrip() for l in content.splitlines()) stmt = inspect.cleandoc( f''' def test_{name}() -> None: content = """{content} """ {stmt.strip()} ''' ) # format it with black stmt = subprocess.check_output( ["black", "-", "-q"], input=stmt.encode("utf-8") ).decode("utf-8") if outfile == "-": print(stmt) else: with open(outfile, "w") as fp: fp.write(stmt) if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("header") parser.add_argument("name", nargs="?", default="TODO") parser.add_argument("--pcpp", default=False, action="store_true") parser.add_argument("-v", "--verbose", default=False, action="store_true") parser.add_argument("-o", "--output", default="-") parser.add_argument( "-x", "--fail", default=False, action="store_true", help="Expect failure" ) args = parser.parse_args() gentest(args.header, args.name, args.output, args.verbose, args.fail, args.pcpp) cxxheaderparser-1.3.1/cxxheaderparser/lexer.py000066400000000000000000000567621455035044000216050ustar00rootroot00000000000000import re import typing import sys from ._ply import lex from ._ply.lex import TOKEN from .errors import CxxParseError class LexError(CxxParseError): pass if sys.version_info >= (3, 8): from typing import Protocol else: Protocol = object _line_re = re.compile(r'^\#[\t ]*(line)? (\d+) "(.*)"') _multicomment_re = re.compile("\n[\\s]+\\*") class Location(typing.NamedTuple): """ Location that something was found at, takes #line directives into account """ filename: str lineno: int class LexToken(Protocol): """ Token as emitted by PLY and modified by our lexer """ #: Lexer type for this token type: str #: Raw value for this token value: str lineno: int lexpos: int #: Location token was found at location: Location #: private lexer: lex.Lexer lexmatch: "re.Match" PhonyEnding: LexToken = lex.LexToken() # type: ignore PhonyEnding.type = "PLACEHOLDER" PhonyEnding.value = "" PhonyEnding.lineno = 0 PhonyEnding.lexpos = 0 class PlyLexer: """ This lexer is a combination of pieces from the PLY lexers that CppHeaderParser and pycparser have. This tokenizes the input into tokens. The other lexer classes do more complex things with the tokens. """ keywords = { "__attribute__", "alignas", "alignof", "asm", "auto", "bool", "break", "case", "catch", "char", "char8_t", "char16_t", "char32_t", "class", "concept", "const", "constexpr", "const_cast", "continue", "decltype", "__declspec", "default", "delete", "do", "double", "dynamic_cast", "else", "enum", "explicit", "export", "extern", "false", "final", "float", "for", "friend", "goto", "if", "inline", "int", "long", "mutable", "namespace", "new", "noexcept", "nullptr", "nullptr_t", # not a keyword, but makes things easier "operator", "private", "protected", "public", "register", "reinterpret_cast", "requires", "return", "short", "signed", "sizeof", "static", "static_assert", "static_cast", "struct", "switch", "template", "this", "thread_local", "throw", "true", "try", "typedef", "typeid", "typename", "union", "unsigned", "using", "virtual", "void", "volatile", "wchar_t", "while", } tokens = [ # constants "FLOAT_CONST", "HEX_FLOAT_CONST", "INT_CONST_HEX", "INT_CONST_BIN", "INT_CONST_OCT", "INT_CONST_DEC", "INT_CONST_CHAR", "CHAR_CONST", "WCHAR_CONST", "U8CHAR_CONST", "U16CHAR_CONST", "U32CHAR_CONST", # String literals "STRING_LITERAL", "WSTRING_LITERAL", "U8STRING_LITERAL", "U16STRING_LITERAL", "U32STRING_LITERAL", # "NAME", # Comments "COMMENT_SINGLELINE", "COMMENT_MULTILINE", "PRAGMA_DIRECTIVE", "INCLUDE_DIRECTIVE", "PP_DIRECTIVE", # misc "DIVIDE", "NEWLINE", "WHITESPACE", "ELLIPSIS", "DBL_LBRACKET", "DBL_RBRACKET", "DBL_COLON", "DBL_AMP", "DBL_PIPE", "ARROW", "SHIFT_LEFT", ] + list(keywords) literals = [ "<", ">", "(", ")", "{", "}", "[", "]", ";", ":", ",", "\\", "|", "%", "^", "!", "*", "-", "+", "&", "=", "'", ".", "?", ] # # Regexes for use in tokens (taken from pycparser) # hex_prefix = "0[xX]" hex_digits = "[0-9a-fA-F']+" bin_prefix = "0[bB]" bin_digits = "[01']+" # integer constants (K&R2: A.2.5.1) integer_suffix_opt = ( r"(([uU]ll)|([uU]LL)|(ll[uU]?)|(LL[uU]?)|([uU][lL])|([lL][uU]?)|[uU])?" ) decimal_constant = ( "(0" + integer_suffix_opt + ")|([1-9][0-9']*" + integer_suffix_opt + ")" ) octal_constant = "0[0-7']*" + integer_suffix_opt hex_constant = hex_prefix + hex_digits + integer_suffix_opt bin_constant = bin_prefix + bin_digits + integer_suffix_opt bad_octal_constant = "0[0-7]*[89]" # character constants (K&R2: A.2.5.2) # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line # directives with Windows paths as filenames (..\..\dir\file) # For the same reason, decimal_escape allows all digit sequences. We want to # parse all correct code, even if it means to sometimes parse incorrect # code. # # The original regexes were taken verbatim from the C syntax definition, # and were later modified to avoid worst-case exponential running time. # # simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" # decimal_escape = r"""(\d+)""" # hex_escape = r"""(x[0-9a-fA-F]+)""" # bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" # # The following modifications were made to avoid the ambiguity that allowed backtracking: # (https://github.com/eliben/pycparser/issues/61) # # - \x was removed from simple_escape, unless it was not followed by a hex digit, to avoid ambiguity with hex_escape. # - hex_escape allows one or more hex characters, but requires that the next character(if any) is not hex # - decimal_escape allows one or more decimal characters, but requires that the next character(if any) is not a decimal # - bad_escape does not allow any decimals (8-9), to avoid conflicting with the permissive decimal_escape. # # Without this change, python's `re` module would recursively try parsing each ambiguous escape sequence in multiple ways. # e.g. `\123` could be parsed as `\1`+`23`, `\12`+`3`, and `\123`. simple_escape = r"""([a-wyzA-Z._~!=&\^\-\\?'"]|x(?![0-9a-fA-F]))""" decimal_escape = r"""(\d+)(?!\d)""" hex_escape = r"""(x[0-9a-fA-F]+)(?![0-9a-fA-F])""" bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-9])""" escape_sequence = ( r"""(\\(""" + simple_escape + "|" + decimal_escape + "|" + hex_escape + "))" ) # This complicated regex with lookahead might be slow for strings, so because all of the valid escapes (including \x) allowed # 0 or more non-escaped characters after the first character, simple_escape+decimal_escape+hex_escape got simplified to escape_sequence_start_in_string = r"""(\\[0-9a-zA-Z._~!=&\^\-\\?'"])""" cconst_char = r"""([^'\\\n]|""" + escape_sequence + ")" char_const = "'" + cconst_char + "'" wchar_const = "L" + char_const u8char_const = "u8" + char_const u16char_const = "u" + char_const u32char_const = "U" + char_const multicharacter_constant = "'" + cconst_char + "{2,4}'" unmatched_quote = "('" + cconst_char + "*\\n)|('" + cconst_char + "*$)" bad_char_const = ( r"""('""" + cconst_char + """[^'\n]+')|('')|('""" + bad_escape + r"""[^'\n]*')""" ) # string literals (K&R2: A.2.6) string_char = r"""([^"\\\n]|""" + escape_sequence_start_in_string + ")" string_literal = '"' + string_char + '*"' wstring_literal = "L" + string_literal u8string_literal = "u8" + string_literal u16string_literal = "u" + string_literal u32string_literal = "U" + string_literal bad_string_literal = '"' + string_char + "*" + bad_escape + string_char + '*"' # floating constants (K&R2: A.2.5.3) exponent_part = r"""([eE][-+]?[0-9]+)""" fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" floating_constant = ( "((((" + fractional_constant + ")" + exponent_part + "?)|([0-9]+" + exponent_part + "))[FfLl]?)" ) binary_exponent_part = r"""([pP][+-]?[0-9]+)""" hex_fractional_constant = ( "(((" + hex_digits + r""")?\.""" + hex_digits + ")|(" + hex_digits + r"""\.))""" ) hex_floating_constant = ( "(" + hex_prefix + "(" + hex_digits + "|" + hex_fractional_constant + ")" + binary_exponent_part + "[FfLl]?)" ) t_WHITESPACE = "[ \t]+" t_ignore = "\r" # The following floating and integer constants are defined as # functions to impose a strict order (otherwise, decimal # is placed before the others because its regex is longer, # and this is bad) # @TOKEN(floating_constant) def t_FLOAT_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(hex_floating_constant) def t_HEX_FLOAT_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(hex_constant) def t_INT_CONST_HEX(self, t: LexToken) -> LexToken: return t @TOKEN(bin_constant) def t_INT_CONST_BIN(self, t: LexToken) -> LexToken: return t @TOKEN(bad_octal_constant) def t_BAD_CONST_OCT(self, t: LexToken) -> None: msg = "Invalid octal constant" self._error(msg, t) @TOKEN(octal_constant) def t_INT_CONST_OCT(self, t: LexToken) -> LexToken: return t @TOKEN(decimal_constant) def t_INT_CONST_DEC(self, t: LexToken) -> LexToken: return t # Must come before bad_char_const, to prevent it from # catching valid char constants as invalid # @TOKEN(multicharacter_constant) def t_INT_CONST_CHAR(self, t: LexToken) -> LexToken: return t @TOKEN(char_const) def t_CHAR_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(wchar_const) def t_WCHAR_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(u8char_const) def t_U8CHAR_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(u16char_const) def t_U16CHAR_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(u32char_const) def t_U32CHAR_CONST(self, t: LexToken) -> LexToken: return t @TOKEN(unmatched_quote) def t_UNMATCHED_QUOTE(self, t: LexToken) -> None: msg = "Unmatched '" self._error(msg, t) @TOKEN(bad_char_const) def t_BAD_CHAR_CONST(self, t: LexToken) -> None: msg = "Invalid char constant %s" % t.value self._error(msg, t) @TOKEN(wstring_literal) def t_WSTRING_LITERAL(self, t: LexToken) -> LexToken: return t @TOKEN(u8string_literal) def t_U8STRING_LITERAL(self, t: LexToken) -> LexToken: return t @TOKEN(u16string_literal) def t_U16STRING_LITERAL(self, t: LexToken) -> LexToken: return t @TOKEN(u32string_literal) def t_U32STRING_LITERAL(self, t: LexToken) -> LexToken: return t # unmatched string literals are caught by the preprocessor @TOKEN(bad_string_literal) def t_BAD_STRING_LITERAL(self, t): msg = "String contains invalid escape code" self._error(msg, t) @TOKEN(r"[A-Za-z_~][A-Za-z0-9_]*") def t_NAME(self, t: LexToken) -> LexToken: if t.value in self.keywords: t.type = t.value return t @TOKEN(r"\#[\t ]*pragma") def t_PRAGMA_DIRECTIVE(self, t: LexToken) -> LexToken: return t @TOKEN(r"\#[\t ]*include (.*)") def t_INCLUDE_DIRECTIVE(self, t: LexToken) -> LexToken: return t @TOKEN(r"\#(.*)") def t_PP_DIRECTIVE(self, t: LexToken): # handle line macros m = _line_re.match(t.value) if m: self.filename = m.group(3) self.line_offset = 1 + self.lex.lineno - int(m.group(2)) return None # ignore C++23 warning directive if t.value.startswith("#warning"): return if "define" in t.value: msgtype = "#define" else: msgtype = "preprocessor" self._error( "cxxheaderparser does not support " + msgtype + " directives, please use a C++ preprocessor first", t, ) t_DIVIDE = r"/(?!/)" t_ELLIPSIS = r"\.\.\." t_DBL_LBRACKET = r"\[\[" t_DBL_RBRACKET = r"\]\]" t_DBL_COLON = r"::" t_DBL_AMP = r"&&" t_DBL_PIPE = r"\|\|" t_ARROW = r"->" t_SHIFT_LEFT = r"<<" # SHIFT_RIGHT introduces ambiguity t_STRING_LITERAL = string_literal @TOKEN(r"\/\/.*\n?") def t_COMMENT_SINGLELINE(self, t: LexToken) -> LexToken: t.lexer.lineno += t.value.count("\n") return t # Found at http://ostermiller.org/findcomment.html @TOKEN(r"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/\n?") def t_COMMENT_MULTILINE(self, t: LexToken) -> LexToken: t.lexer.lineno += t.value.count("\n") return t @TOKEN(r"\n+") def t_NEWLINE(self, t: LexToken) -> LexToken: t.lexer.lineno += len(t.value) return t def t_error(self, t: LexToken) -> None: self._error(f"Illegal character {t.value!r}", t) def _error(self, msg: str, tok: LexToken): tok.location = self.current_location() raise LexError(msg, tok) _lexer = None lex: lex.Lexer def __new__(cls, *args, **kwargs) -> "PlyLexer": # only build the lexer once inst = super().__new__(cls) if cls._lexer is None: cls._lexer = lex.lex(module=inst) inst.lex = cls._lexer.clone(inst) inst.lex.begin("INITIAL") return inst def __init__(self, filename: typing.Optional[str] = None): self.input: typing.Callable[[str], None] = self.lex.input self.token: typing.Callable[[], LexToken] = self.lex.token # For tracking current file/line position self.filename = filename self.line_offset = 0 def current_location(self) -> Location: return Location(self.filename, self.lex.lineno - self.line_offset) class TokenStream: """ Provides access to a stream of tokens """ tokbuf: typing.Deque[LexToken] def _fill_tokbuf(self, tokbuf: typing.Deque[LexToken]) -> bool: """ Fills tokbuf with tokens from the next line. Return True if at least one token was added to the buffer """ raise NotImplementedError def current_location(self) -> Location: raise NotImplementedError def get_doxygen(self) -> typing.Optional[str]: """ This is called at the point that you want doxygen information """ raise NotImplementedError def get_doxygen_after(self) -> typing.Optional[str]: """ This is called to retrieve doxygen information after a statement """ raise NotImplementedError _discard_types = { "NEWLINE", "COMMENT_SINGLELINE", "COMMENT_MULTILINE", "WHITESPACE", } _discard_types_except_newline = { "COMMENT_SINGLELINE", "COMMENT_MULTILINE", "WHITESPACE", } def token(self) -> LexToken: tokbuf = self.tokbuf while True: while tokbuf: tok = tokbuf.popleft() if tok.type not in self._discard_types: return tok if not self._fill_tokbuf(tokbuf): raise EOFError("unexpected end of file") def token_eof_ok(self) -> typing.Optional[LexToken]: tokbuf = self.tokbuf while True: while tokbuf: tok = tokbuf.popleft() if tok.type not in self._discard_types: return tok if not self._fill_tokbuf(tokbuf): return None def token_newline_eof_ok(self) -> typing.Optional[LexToken]: tokbuf = self.tokbuf while True: while tokbuf: tok = tokbuf.popleft() if tok.type not in self._discard_types_except_newline: return tok if not self._fill_tokbuf(tokbuf): return None def token_if(self, *types: str) -> typing.Optional[LexToken]: tok = self.token_eof_ok() if tok is None: return None if tok.type not in types: self.tokbuf.appendleft(tok) return None return tok def token_if_in_set(self, types: typing.Set[str]) -> typing.Optional[LexToken]: tok = self.token_eof_ok() if tok is None: return None if tok.type not in types: self.tokbuf.appendleft(tok) return None return tok def token_if_val(self, *vals: str) -> typing.Optional[LexToken]: tok = self.token_eof_ok() if tok is None: return None if tok.value not in vals: self.tokbuf.appendleft(tok) return None return tok def token_if_not(self, *types: str) -> typing.Optional[LexToken]: tok = self.token_eof_ok() if tok is None: return None if tok.type in types: self.tokbuf.appendleft(tok) return None return tok def token_peek_if(self, *types: str) -> bool: tok = self.token_eof_ok() if not tok: return False self.tokbuf.appendleft(tok) return tok.type in types def return_token(self, tok: LexToken) -> None: self.tokbuf.appendleft(tok) def return_tokens(self, toks: typing.Sequence[LexToken]) -> None: self.tokbuf.extendleft(reversed(toks)) class LexerTokenStream(TokenStream): """ Provides tokens from using PlyLexer on the given input text """ _user_defined_literal_start = { "FLOAT_CONST", "HEX_FLOAT_CONST", "INT_CONST_HEX", "INT_CONST_BIN", "INT_CONST_OCT", "INT_CONST_DEC", "INT_CONST_CHAR", "CHAR_CONST", "WCHAR_CONST", "U8CHAR_CONST", "U16CHAR_CONST", "U32CHAR_CONST", # String literals "STRING_LITERAL", "WSTRING_LITERAL", "U8STRING_LITERAL", "U16STRING_LITERAL", "U32STRING_LITERAL", } def __init__(self, filename: typing.Optional[str], content: str) -> None: self._lex = PlyLexer(filename) self._lex.input(content) self.tokbuf = typing.Deque[LexToken]() def _fill_tokbuf(self, tokbuf: typing.Deque[LexToken]) -> bool: get_token = self._lex.token tokbuf = self.tokbuf tok = get_token() if tok is None: return False udl_start = self._user_defined_literal_start while True: tok.location = self._lex.current_location() tokbuf.append(tok) if tok.type == "NEWLINE": # detect/remove line continuations if len(tokbuf) > 2 and tokbuf[-2].type == "\\": tokbuf.pop() tokbuf.pop() else: break # detect/combine user defined literals if tok.type in udl_start: tok2 = get_token() if tok2 is None: break if tok2.type != "NAME" or tok2.value[0] != "_": tok = tok2 continue tok.value = tok.value + tok2.value tok.type = f"UD_{tok.type}" tok = get_token() if tok is None: break return True def current_location(self) -> Location: if self.tokbuf: return self.tokbuf[0].location return self._lex.current_location() def get_doxygen(self) -> typing.Optional[str]: tokbuf = self.tokbuf # fill the token buffer if it's empty (which indicates a newline) if not tokbuf and not self._fill_tokbuf(tokbuf): return None comments: typing.List[LexToken] = [] # retrieve any comments in the stream right before # the first non-discard element keep_going = True while True: while tokbuf: tok = tokbuf.popleft() if tok.type == "NEWLINE": comments.clear() elif tok.type == "WHITESPACE": pass elif tok.type in ("COMMENT_SINGLELINE", "COMMENT_MULTILINE"): comments.append(tok) else: tokbuf.appendleft(tok) keep_going = False break if not keep_going: break if not self._fill_tokbuf(tokbuf): break if comments: return self._extract_comments(comments) return None def get_doxygen_after(self) -> typing.Optional[str]: tokbuf = self.tokbuf # if there's a newline directly after a statement, we're done if not tokbuf: return None # retrieve comments after non-discard elements comments: typing.List[LexToken] = [] new_tokbuf = typing.Deque[LexToken]() # This is different: we only extract tokens here while tokbuf: tok = tokbuf.popleft() if tok.type == "NEWLINE": break elif tok.type == "WHITESPACE": new_tokbuf.append(tok) elif tok.type in ("COMMENT_SINGLELINE", "COMMENT_MULTILINE"): comments.append(tok) else: new_tokbuf.append(tok) if comments: break new_tokbuf.extend(tokbuf) self.tokbuf = new_tokbuf if comments: return self._extract_comments(comments) return None def _extract_comments(self, comments: typing.List[LexToken]): # Now we have comments, need to extract the text from them comment_lines: typing.List[str] = [] for c in comments: text = c.value if c.type == "COMMENT_SINGLELINE": if text.startswith("///") or text.startswith("//!"): comment_lines.append(text.rstrip("\n")) else: if text.startswith("/**") or text.startswith("/*!"): # not sure why, but get double new lines text = text.replace("\n\n", "\n") # strip prefixing whitespace text = _multicomment_re.sub("\n*", text) comment_lines = text.splitlines() comment_str = "\n".join(comment_lines) if comment_str: return comment_str return None class BoundedTokenStream(TokenStream): """ Provides tokens from a fixed list of tokens. Intended for use when you have a group of tokens that you know must be consumed, such as a paren grouping or some type of lookahead case """ def __init__(self, toks: typing.List[LexToken]) -> None: self.tokbuf = typing.Deque[LexToken](toks) def has_tokens(self) -> bool: return len(self.tokbuf) > 0 def _fill_tokbuf(self, tokbuf: typing.Deque[LexToken]) -> bool: raise CxxParseError("no more tokens left in this group") def current_location(self) -> Location: if self.tokbuf: return self.tokbuf[0].location raise ValueError("internal error") def get_doxygen(self) -> typing.Optional[str]: # comment tokens aren't going to be in this stream return None def get_doxygen_after(self) -> typing.Optional[str]: return None if __name__ == "__main__": # pragma: no cover try: lex.runmain(lexer=PlyLexer(None)) except EOFError: pass cxxheaderparser-1.3.1/cxxheaderparser/options.py000066400000000000000000000011671455035044000221460ustar00rootroot00000000000000from dataclasses import dataclass from typing import Callable, Optional #: arguments are (filename, content) PreprocessorFunction = Callable[[str, Optional[str]], str] @dataclass class ParserOptions: """ Options that control parsing behaviors """ #: If true, prints out verbose: bool = False #: If true, converts a single void parameter to zero parameters convert_void_to_zero_params: bool = True #: A function that will preprocess the header before parsing. See #: :py:mod:`cxxheaderparser.preprocessor` for available preprocessors preprocessor: Optional[PreprocessorFunction] = None cxxheaderparser-1.3.1/cxxheaderparser/parser.py000066400000000000000000002652061455035044000217550ustar00rootroot00000000000000from collections import deque import inspect import re import typing from . import lexer from .errors import CxxParseError from .lexer import LexToken, Location, PhonyEnding from .options import ParserOptions from .parserstate import ( ClassBlockState, ExternBlockState, NamespaceBlockState, NonClassBlockState, ParsedTypeModifiers, State, ) from .types import ( AnonymousName, Array, AutoSpecifier, BaseClass, ClassDecl, Concept, DecltypeSpecifier, DecoratedType, DeductionGuide, EnumDecl, Enumerator, Field, ForwardDecl, FriendDecl, Function, FunctionType, FundamentalSpecifier, Method, MoveReference, NameSpecifier, NamespaceAlias, NamespaceDecl, PQNameSegment, Parameter, PQName, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateDeclTypeVar, TemplateInst, TemplateNonTypeParam, TemplateParam, TemplateSpecialization, TemplateTypeParam, Token, Type, Typedef, UsingAlias, UsingDecl, Value, Variable, ) from .visitor import CxxVisitor, null_visitor LexTokenList = typing.List[LexToken] T = typing.TypeVar("T") PT = typing.TypeVar("PT", Parameter, TemplateNonTypeParam) class CxxParser: """ Single-use parser object """ def __init__( self, filename: str, content: typing.Optional[str], visitor: CxxVisitor, options: typing.Optional[ParserOptions] = None, encoding: typing.Optional[str] = None, ) -> None: self.visitor = visitor self.filename = filename self.options = options if options else ParserOptions() if options and options.preprocessor is not None: content = options.preprocessor(filename, content) if content is None: if encoding is None: encoding = "utf-8-sig" with open(filename, "r", encoding=encoding) as fp: content = fp.read() self.lex: lexer.TokenStream = lexer.LexerTokenStream(filename, content) global_ns = NamespaceDecl([], False) self.current_namespace = global_ns self.state: State = NamespaceBlockState( None, self.lex.current_location(), global_ns ) self.anon_id = 0 self.verbose = self.options.verbose if self.verbose: def debug_print(fmt: str, *args: typing.Any) -> None: fmt = f"[%4d] {fmt}" args = (inspect.currentframe().f_back.f_lineno,) + args # type: ignore print(fmt % args) self.debug_print = debug_print else: self.debug_print = lambda fmt, *args: None self.visitor.on_parse_start(self.state) # # State management # def _setup_state(self, state: State): state._prior_visitor = self.visitor self.state = state def _pop_state(self) -> State: prev_state = self.state prev_state._finish(self.visitor) self.visitor = prev_state._prior_visitor state = prev_state.parent if state is None: raise CxxParseError("INTERNAL ERROR: unbalanced state") if isinstance(state, NamespaceBlockState): self.current_namespace = state.namespace self.state = state return prev_state @property def _current_access(self) -> typing.Optional[str]: return getattr(self.state, "access", None) # # Utility parsing functions used by the rest of the code # def _parse_error( self, tok: typing.Optional[LexToken], expected: str = "" ) -> CxxParseError: if not tok: # common case after a failed token_if tok = self.lex.token() if expected: expected = f", expected '{expected}'" msg = f"unexpected '{tok.value}'{expected}" # TODO: better error message return CxxParseError(msg, tok) def _next_token_must_be(self, *tokenTypes: str) -> LexToken: tok = self.lex.token() if tok.type not in tokenTypes: raise self._parse_error(tok, "' or '".join(tokenTypes)) return tok # def _next_token_in_set(self, tokenTypes: typing.Set[str]) -> LexToken: # tok = self.lex.token() # if tok.type not in tokenTypes: # raise self._parse_error(tok, "' or '".join(sorted(tokenTypes))) # return tok # def _consume_up_to(self, rtoks: LexTokenList, *token_types: str) -> LexTokenList: # # includes the last token # get_token = self.lex.token # while True: # tok = get_token() # rtoks.append(tok) # if tok.type in token_types: # break # return rtoks def _consume_until(self, rtoks: LexTokenList, *token_types: str) -> LexTokenList: # does not include the found token token_if_not = self.lex.token_if_not while True: tok = token_if_not(*token_types) if tok is None: break rtoks.append(tok) return rtoks def _consume_value_until( self, rtoks: LexTokenList, *token_types: str ) -> LexTokenList: # does not include the found token token_if_not = self.lex.token_if_not while True: tok = token_if_not(*token_types) if tok is None: break if tok.type in self._balanced_token_map: rtoks.extend(self._consume_balanced_tokens(tok)) else: rtoks.append(tok) return rtoks _end_balanced_tokens = {">", "}", "]", ")", "DBL_RBRACKET"} _balanced_token_map = { "<": ">", "{": "}", "(": ")", "[": "]", "DBL_LBRACKET": "DBL_RBRACKET", } def _consume_balanced_tokens( self, *init_tokens: LexToken, token_map: typing.Optional[typing.Dict[str, str]] = None, ) -> LexTokenList: if token_map is None: token_map = self._balanced_token_map consumed = list(init_tokens) match_stack = deque((token_map[tok.type] for tok in consumed)) get_token = self.lex.token tok: typing.Optional[LexToken] while True: tok = get_token() consumed.append(tok) if tok.type in self._end_balanced_tokens: expected = match_stack.pop() if tok.type != expected: # hack: we only claim to parse correct code, so if this # is less than or greater than, assume that the code is # doing math and so this unexpected item is correct. # # If one of the other items on the stack match, pop back # to that. Otherwise, ignore it and hope for the best if tok.type != ">" and expected != ">": raise self._parse_error(tok, expected) for i, maybe in enumerate(reversed(match_stack)): if tok.type == maybe: for _ in range(i + 1): match_stack.pop() break else: match_stack.append(expected) continue if len(match_stack) == 0: return consumed continue next_end = token_map.get(tok.type) if next_end: match_stack.append(next_end) def _discard_contents(self, start_type: str, end_type: str) -> None: # use this instead of consume_balanced_tokens because # we don't care at all about the internals level = 1 get_token = self.lex.token while True: tok = get_token() if tok.type == start_type: level += 1 elif tok.type == end_type: level -= 1 if level == 0: break def _create_value(self, toks: LexTokenList) -> Value: return Value([Token(tok.value, tok.type) for tok in toks]) # # Parsing begins here # def parse(self) -> None: """ Parse the header contents """ # non-ambiguous parsing functions for each token type _translation_unit_tokens: typing.Dict[ str, typing.Callable[[LexToken, typing.Optional[str]], typing.Any] ] = { "__attribute__": self._consume_gcc_attribute, "__declspec": self._consume_declspec, "alignas": self._consume_attribute_specifier_seq, "extern": self._parse_extern, "friend": self._parse_friend_decl, "inline": self._parse_inline, "namespace": self._parse_namespace, "private": self._process_access_specifier, "protected": self._process_access_specifier, "public": self._process_access_specifier, "static_assert": self._consume_static_assert, "template": self._parse_template, "typedef": self._parse_typedef, "using": self._parse_using, "{": self._on_empty_block_start, "}": self._on_block_end, "DBL_LBRACKET": self._consume_attribute_specifier_seq, "INCLUDE_DIRECTIVE": self._process_include_directive, "PRAGMA_DIRECTIVE": self._process_pragma_directive, ";": lambda _1, _2: None, } _keep_doxygen = {"__declspec", "alignas", "__attribute__", "DBL_LBRACKET"} tok = None get_token_eof_ok = self.lex.token_eof_ok get_doxygen = self.lex.get_doxygen doxygen = None try: while True: if doxygen is None: doxygen = get_doxygen() tok = get_token_eof_ok() if not tok: break fn = _translation_unit_tokens.get(tok.type) if fn: fn(tok, doxygen) if tok.type not in _keep_doxygen: doxygen = None else: # this processes ambiguous declarations self._parse_declarations(tok, doxygen) doxygen = None except Exception as e: if self.verbose: raise context = "" if isinstance(e, CxxParseError): context = ": " + str(e) if e.tok: tok = e.tok if tok: filename, lineno = tok.location msg = f"{filename}:{lineno}: parse error evaluating '{tok.value}'{context}" else: msg = f"{self.filename}: parse error{context}" raise CxxParseError(msg) from e # # Preprocessor directives # _preprocessor_compress_re = re.compile(r"^#[\t ]+") _preprocessor_split_re = re.compile(r"[\t ]+") def _process_include_directive(self, tok: LexToken, doxygen: typing.Optional[str]): value = self._preprocessor_compress_re.sub("#", tok.value) svalue = self._preprocessor_split_re.split(value, 1) if len(svalue) == 2: self.state.location = tok.location self.visitor.on_include(self.state, svalue[1]) else: raise CxxParseError("incomplete #include directive", tok) def _process_pragma_directive(self, _: LexToken, doxygen: typing.Optional[str]): # consume all tokens until the end of the line # -- but if we find a paren, get the group tokens: LexTokenList = [] while True: tok = self.lex.token_newline_eof_ok() if not tok or tok.type == "NEWLINE": break if tok.type in self._balanced_token_map: tokens.extend(self._consume_balanced_tokens(tok)) else: tokens.append(tok) self.visitor.on_pragma(self.state, self._create_value(tokens)) # # Various # _msvc_conventions = { "__cdecl", "__clrcall", "__stdcall", "__fastcall", "__thiscall", "__vectorcall", } def _parse_namespace( self, tok: LexToken, doxygen: typing.Optional[str], inline: bool = False ) -> None: """ namespace_definition: named_namespace_definition | unnamed_namespace_definition named_namespace_definition: ["inline"] "namespace" IDENTIFIER "{" namespace_body "}" unnamed_namespace_definition: ["inline"] "namespace" "{" namespace_body "}" namespace_alias_definition: "namespace" IDENTIFIER "=" qualified_namespace_specifier ";" """ names = [] location = tok.location ns_alias: typing.Union[typing.Literal[False], LexToken] = False tok = self._next_token_must_be("NAME", "{") if tok.type != "{": endtok = "{" # Check for namespace alias here etok = self.lex.token_if("=") if etok: ns_alias = tok endtok = ";" # They can start with :: maybe_tok = self.lex.token_if("DBL_COLON") if maybe_tok: names.append(maybe_tok.value) tok = self._next_token_must_be("NAME") while True: names.append(tok.value) tok = self._next_token_must_be("DBL_COLON", endtok) if tok.type == endtok: break tok = self._next_token_must_be("NAME") if inline and len(names) > 1: raise CxxParseError("a nested namespace definition cannot be inline") state = self.state if not isinstance(state, (NamespaceBlockState, ExternBlockState)): raise CxxParseError("namespace cannot be defined in a class") if ns_alias: alias = NamespaceAlias(ns_alias.value, names) self.visitor.on_namespace_alias(state, alias) return ns = NamespaceDecl(names, inline, doxygen) state = NamespaceBlockState(state, location, ns) self._setup_state(state) self.current_namespace = state.namespace if self.visitor.on_namespace_start(state) is False: self.visitor = null_visitor def _parse_extern(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: etok = self.lex.token_if("STRING_LITERAL", "template") if etok: # classes cannot contain extern blocks/templates state = self.state if isinstance(state, ClassBlockState): raise self._parse_error(tok) if etok.type == "STRING_LITERAL": if self.lex.token_if("{"): state = ExternBlockState(state, tok.location, etok.value) self._setup_state(state) if self.visitor.on_extern_block_start(state) is False: self.visitor = null_visitor return # an extern variable/function with specific linkage self.lex.return_token(etok) else: # must be an extern template instantitation self._parse_template_instantiation(doxygen, True) return self._parse_declarations(tok, doxygen) def _parse_friend_decl( self, tok: LexToken, doxygen: typing.Optional[str], template: typing.Optional[TemplateDecl] = None, ) -> None: if not isinstance(self.state, ClassBlockState): raise self._parse_error(tok) tok = self.lex.token() self._parse_declarations(tok, doxygen, template, is_friend=True) def _parse_inline(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: itok = self.lex.token_if("namespace") if itok: self._parse_namespace(itok, doxygen, inline=True) else: self._parse_declarations(tok, doxygen) def _parse_typedef(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: tok = self.lex.token() self._parse_declarations(tok, doxygen, is_typedef=True) def _consume_static_assert( self, tok: LexToken, doxygen: typing.Optional[str] ) -> None: self._next_token_must_be("(") self._discard_contents("(", ")") def _on_empty_block_start( self, tok: LexToken, doxygen: typing.Optional[str] ) -> None: raise self._parse_error(tok) def _on_block_end(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: old_state = self._pop_state() if isinstance(old_state, ClassBlockState): self._finish_class_decl(old_state) # # Template and concept parsing # def _parse_template_type_parameter( self, tok: LexToken, template: typing.Optional[TemplateDecl] ) -> TemplateTypeParam: """ type_parameter: "class" ["..."] [IDENTIFIER] | "class" [IDENTIFIER] "=" type_id | "typename" ["..."] [IDENTIFIER] | "typename" [IDENTIFIER] "=" type_id | "template" "<" template_parameter_list ">" "class" ["..."] [IDENTIFIER] | "template" "<" template_parameter_list ">" "class" [IDENTIFIER] "=" id_expression """ # entry: token is either class or typename typekey = tok.type param_pack = True if self.lex.token_if("ELLIPSIS") else False name = None default = None otok = self.lex.token_if("NAME") if otok: name = otok.value otok = self.lex.token_if("=") if otok: default = self._create_value(self._consume_value_until([], ",", ">")) return TemplateTypeParam(typekey, name, param_pack, default, template) def _parse_template_decl(self) -> TemplateDecl: """ template_declaration: "template" "<" template_parameter_list ">" declaration explicit_specialization: "template" "<" ">" declaration template_parameter_list: template_parameter | template_parameter_list "," template_parameter template_parameter: type_parameter | parameter_declaration """ tok = self._next_token_must_be("<") params: typing.List[TemplateParam] = [] lex = self.lex if not lex.token_if(">"): while True: tok = lex.token() tok_type = tok.type param: TemplateParam if tok_type == "template": template = self._parse_template_decl() tok = self._next_token_must_be("class", "typename") param = self._parse_template_type_parameter(tok, template) elif tok_type == "class": param = self._parse_template_type_parameter(tok, None) elif tok_type == "typename": ptok = lex.token() if ptok.type in ("ELLIPSIS", "=", ",", ">") or ( ptok.type == "NAME" and lex.token_peek_if("=", ",", ">") ): lex.return_token(ptok) param = self._parse_template_type_parameter(tok, None) else: param, _ = self._parse_parameter( ptok, TemplateNonTypeParam, False, ">" ) else: param, _ = self._parse_parameter( tok, TemplateNonTypeParam, concept_ok=False, end=">" ) params.append(param) tok = self._next_token_must_be(",", ">") if tok.type == ">": break return TemplateDecl(params) def _parse_template(self, tok: LexToken, doxygen: typing.Optional[str]) -> None: if not self.lex.token_peek_if("<"): self._parse_template_instantiation(doxygen, False) return template = self._parse_template_decl() # Check for multiple specializations tok = self.lex.token() if tok.type == "template": templates = [template] while tok.type == "template": templates.append(self._parse_template_decl()) tok = self.lex.token() # Can only be followed by declarations self._parse_declarations(tok, doxygen, templates) return if tok.type == "using": self._parse_using(tok, doxygen, template) elif tok.type == "friend": self._parse_friend_decl(tok, doxygen, template) elif tok.type == "concept": self._parse_concept(tok, doxygen, template) elif tok.type == "requires": template.raw_requires_pre = self._parse_requires(tok) self._parse_declarations(self.lex.token(), doxygen, template) else: self._parse_declarations(tok, doxygen, template) def _parse_template_specialization(self) -> TemplateSpecialization: """ template_argument_list: template_argument ["..."] | template_argument_list "," template_argument ["..."] template_argument: constant_expression | type_id | id_expression template_id: simple_template_id | operator_function_id "<" [template_argument_list] ">" | literal_operator_id "<" [template_argument_list] ">" simple_template_id: IDENTIFIER "<" [template_argument_list] ">" """ args: typing.List[TemplateArgument] = [] # On entry, < has just been consumed while True: # We don't know whether each argument will be a type or an expression. # Retrieve the expression first, then try to parse the name using those # tokens. If it succeeds we're done, otherwise we use the value param_pack = False raw_toks = self._consume_value_until([], ",", ">", "ELLIPSIS") val = self._create_value(raw_toks) dtype = None if raw_toks and raw_toks[0].type in self._pqname_start_tokens: # append a token to make other parsing components happy raw_toks.append(PhonyEnding) old_lex = self.lex try: # set up a temporary token stream with the tokens we need to parse tmp_lex = lexer.BoundedTokenStream(raw_toks) self.lex = tmp_lex try: parsed_type, mods = self._parse_type(None) if parsed_type is None: raise self._parse_error(None) mods.validate(var_ok=False, meth_ok=False, msg="") dtype = self._parse_cv_ptr_or_fn(parsed_type, nonptr_fn=True) self._next_token_must_be(PhonyEnding.type) except CxxParseError: dtype = None else: if tmp_lex.has_tokens(): dtype = None finally: self.lex = old_lex if self.lex.token_if("ELLIPSIS"): param_pack = True if dtype: args.append(TemplateArgument(dtype, param_pack)) else: args.append(TemplateArgument(val, param_pack)) tok = self._next_token_must_be(",", ">") if tok.type == ">": break return TemplateSpecialization(args) def _parse_template_instantiation( self, doxygen: typing.Optional[str], extern: bool ): """ explicit-instantiation: [extern] template declaration """ # entry is right after template tok = self.lex.token_if("class", "struct") if not tok: raise self._parse_error(tok) atok = self.lex.token_if_in_set(self._attribute_start_tokens) if atok: self._consume_attribute(atok) typename, _ = self._parse_pqname(None) # the last segment must have a specialization last_segment = typename.segments[-1] if ( not isinstance(last_segment, NameSpecifier) or not last_segment.specialization ): raise self._parse_error( None, "expected extern template to have specialization" ) self._next_token_must_be(";") self.visitor.on_template_inst( self.state, TemplateInst(typename, extern, doxygen) ) def _parse_concept( self, tok: LexToken, doxygen: typing.Optional[str], template: TemplateDecl, ) -> None: name = self._next_token_must_be("NAME") self._next_token_must_be("=") # not trying to understand this for now raw_constraint = self._create_value(self._consume_value_until([], ",", ";")) state = self.state if isinstance(state, ClassBlockState): raise CxxParseError("concept cannot be defined in a class") self.visitor.on_concept( state, Concept( template=template, name=name.value, raw_constraint=raw_constraint, doxygen=doxygen, ), ) # fmt: off _expr_operators = { "<", ">", "|", "%", "^", "!", "*", "-", "+", "&", "=", "&&", "||", "<<" } # fmt: on def _parse_requires( self, tok: LexToken, ) -> Value: tok = self.lex.token() rawtoks: typing.List[LexToken] = [] # The easier case -- requires requires if tok.type == "requires": rawtoks.append(tok) for tt in ("(", "{"): tok = self._next_token_must_be(tt) rawtoks.extend(self._consume_balanced_tokens(tok)) # .. and that's it? # this is either a parenthesized expression or a primary clause elif tok.type == "(": rawtoks.extend(self._consume_balanced_tokens(tok)) else: while True: if tok.type == "(": rawtoks.extend(self._consume_balanced_tokens(tok)) else: tok = self._parse_requires_segment(tok, rawtoks) # If this is not an operator of some kind, we don't know how # to proceed so let the next parser figure it out if tok.value not in self._expr_operators: break rawtoks.append(tok) # check once more for compound operator? tok = self.lex.token() if tok.value in self._expr_operators: rawtoks.append(tok) tok = self.lex.token() self.lex.return_token(tok) return self._create_value(rawtoks) def _parse_requires_segment( self, tok: LexToken, rawtoks: typing.List[LexToken] ) -> LexToken: # first token could be a name or :: if tok.type == "DBL_COLON": rawtoks.append(tok) tok = self.lex.token() while True: # This token has to be a name or some other valid name-like thing if tok.value == "decltype": rawtoks.append(tok) tok = self._next_token_must_be("(") rawtoks.extend(self._consume_balanced_tokens(tok)) elif tok.type == "NAME": rawtoks.append(tok) else: # not sure what I expected, but I didn't find it raise self._parse_error(tok) tok = self.lex.token() # Maybe there's a specialization if tok.value == "<": rawtoks.extend(self._consume_balanced_tokens(tok)) tok = self.lex.token() # Maybe we keep trying to parse this name if tok.type == "DBL_COLON": tok = self.lex.token() continue # Let the caller decide return tok # # Attributes # _attribute_specifier_seq_start_types = {"DBL_LBRACKET", "alignas"} _attribute_start_tokens = { "__attribute__", "__declspec", } _attribute_start_tokens |= _attribute_specifier_seq_start_types def _consume_attribute(self, tok: LexToken) -> None: if tok.type == "__attribute__": self._consume_gcc_attribute(tok) elif tok.type == "__declspec": self._consume_declspec(tok) elif tok.type in self._attribute_specifier_seq_start_types: self._consume_attribute_specifier_seq(tok) else: raise CxxParseError("internal error") def _consume_gcc_attribute( self, tok: LexToken, doxygen: typing.Optional[str] = None ) -> None: tok1 = self._next_token_must_be("(") tok2 = self._next_token_must_be("(") self._consume_balanced_tokens(tok1, tok2) def _consume_declspec( self, tok: LexToken, doxygen: typing.Optional[str] = None ) -> None: tok = self._next_token_must_be("(") self._consume_balanced_tokens(tok) def _consume_attribute_specifier_seq( self, tok: LexToken, doxygen: typing.Optional[str] = None ) -> None: """ attribute_specifier_seq: attribute_specifier | attribute_specifier_seq attribute_specifier attribute_specifier: "[[" attribute_list "]]" | alignment_specifier alignment_specifier: "alignas" "(" type_id ["..."] ")" | "alignas" "(" alignment_expression ["..."] ")" attribute_list: [attribute] | attribute_list "," [attribute] | attribute "..." | attribute_list "," attribute "..." attribute: attribute_token [attribute_argument_clause] attribute_token: IDENTIFIER | attribute_scoped_token attribute_scoped_token: attribute_namespace "::" IDENTIFIER attribute_namespace: IDENTIFIER attribute_argument_clause: "(" balanced_token_seq ")" balanced_token_seq: balanced_token | balanced_token_seq balanced_token balanced_token: "(" balanced_token_seq ")" | "[" balanced_token_seq "]" | "{" balanced_token_seq "}" | token """ # TODO: retain the attributes and do something with them # attrs = [] while True: if tok.type == "DBL_LBRACKET": tokens = self._consume_balanced_tokens(tok) # attrs.append(Attribute(tokens)) elif tok.type == "alignas": next_tok = self._next_token_must_be("(") tokens = self._consume_balanced_tokens(next_tok) # attrs.append(AlignasAttribute(tokens)) else: self.lex.return_token(tok) break # multiple attributes can be specified maybe_tok = self.lex.token_if(*self._attribute_specifier_seq_start_types) if maybe_tok is None: break tok = maybe_tok # TODO return attrs # # Using directive/declaration/typealias # def _parse_using_directive(self, state: NonClassBlockState) -> None: """ using_directive: [attribute_specifier_seq] "using" "namespace" ["::"] [nested_name_specifier] IDENTIFIER ";" """ names = [] if self.lex.token_if("DBL_COLON"): names.append("") while True: tok = self._next_token_must_be("NAME") names.append(tok.value) if not self.lex.token_if("DBL_COLON"): break if not names: raise self._parse_error(None, "NAME") self.visitor.on_using_namespace(state, names) def _parse_using_declaration( self, tok: LexToken, doxygen: typing.Optional[str] ) -> None: """ using_declaration: "using" ["typename"] ["::"] nested_name_specifier unqualified_id ";" | "using" "::" unqualified_id ";" """ if tok.type == "typename": tok = self.lex.token() typename, _ = self._parse_pqname( tok, fn_ok=True, compound_ok=True, fund_ok=True ) decl = UsingDecl(typename, self._current_access, doxygen) self.visitor.on_using_declaration(self.state, decl) def _parse_using_typealias( self, id_tok: LexToken, template: typing.Optional[TemplateDecl], doxygen: typing.Optional[str], ) -> None: """ alias_declaration: "using" IDENTIFIER "=" type_id ";" """ parsed_type, mods = self._parse_type(None) if parsed_type is None: raise self._parse_error(None) mods.validate(var_ok=False, meth_ok=False, msg="parsing typealias") dtype = self._parse_cv_ptr(parsed_type) alias = UsingAlias(id_tok.value, dtype, template, self._current_access, doxygen) self.visitor.on_using_alias(self.state, alias) def _parse_using( self, tok: LexToken, doxygen: typing.Optional[str], template: typing.Optional[TemplateDecl] = None, ) -> None: self.state.location = tok.location tok = self._next_token_must_be("NAME", "DBL_COLON", "namespace", "typename") if tok.type == "namespace": if template: raise CxxParseError( "unexpected using-directive when parsing alias-declaration", tok ) state = self.state if not isinstance(state, (NamespaceBlockState, ExternBlockState)): raise self._parse_error(tok) self._parse_using_directive(state) elif tok.type in ("DBL_COLON", "typename") or not self.lex.token_if("="): if template: raise CxxParseError( "unexpected using-declaration when parsing alias-declaration", tok ) self._parse_using_declaration(tok, doxygen) else: self._parse_using_typealias(tok, template, doxygen) # All using things end with a semicolon self._next_token_must_be(";") # # Enum parsing # def _parse_enum_decl( self, typename: PQName, tok: LexToken, doxygen: typing.Optional[str], is_typedef: bool, location: Location, mods: ParsedTypeModifiers, ) -> None: """ opaque_enum_declaration: enum_key [attribute_specifier_seq] IDENTIFIER [enum_base] ";" enum_specifier: enum_head "{" [enumerator_list] "}" | enum_head "{" enumerator_list "," "}" enum_head: enum_key [attribute_specifier_seq] [IDENTIFIER] [enum_base] | enum_key [attribute_specifier_seq] nested_name_specifier IDENTIFIER [enum_base] enum_key: "enum" | "enum" "class" | "enum" "struct" enum_base: ":" type_specifier_seq """ self.state.location = location tok_type = tok.type # entry: tok is one of _class_enum_stage2 if tok_type not in (":", "{"): raise self._parse_error(tok) base = None values: typing.List[Enumerator] = [] if tok_type == ":": base, _ = self._parse_pqname(None, fund_ok=True) tok = self._next_token_must_be("{", ";") tok_type = tok.type if tok_type == ";": if is_typedef: raise self._parse_error(tok) # enum forward declaration with base fdecl = ForwardDecl( typename, None, doxygen, base, access=self._current_access ) self.visitor.on_forward_decl(self.state, fdecl) return values = self._parse_enumerator_list() enum = EnumDecl(typename, values, base, doxygen, self._current_access) self.visitor.on_enum(self.state, enum) # Finish it up self._finish_class_or_enum(enum.typename, is_typedef, mods, "enum") def _parse_enumerator_list(self) -> typing.List[Enumerator]: """ enumerator_list: enumerator_definition | enumerator_list "," enumerator_definition enumerator_definition: enumerator | enumerator "=" constant_expression enumerator: IDENTIFIER [attribute-specifier-seq] """ values: typing.List[Enumerator] = [] while True: doxygen = self.lex.get_doxygen() name_tok = self._next_token_must_be("}", "NAME") if name_tok.value == "}": break if doxygen is None: doxygen = self.lex.get_doxygen_after() name = name_tok.value value = None tok = self._next_token_must_be("}", ",", "=", "DBL_LBRACKET") if tok.type == "DBL_LBRACKET": self._consume_attribute_specifier_seq(tok) tok = self._next_token_must_be("}", ",", "=") if tok.type == "=": value = self._create_value(self._consume_value_until([], ",", "}")) tok = self._next_token_must_be("}", ",") values.append(Enumerator(name, value, doxygen)) if tok.type == "}": break return values # # Type parsing # _base_access_virtual = {"public", "private", "protected", "virtual"} def _parse_class_decl_base_clause( self, default_access: str ) -> typing.List[BaseClass]: """ base_clause: ":" base_specifier_list base_specifier_list: base_specifier ["..."] | base_specifier_list "," base_specifier ["..."] base_specifier: [attribute_specifier_seq] base_type_specifier | [attribute_specifier_seq] "virtual" [access_specifier] base_type_specifier | [attribute_specifier_seq] access_specifier ["virtual"] base_type_specifier base_type_specifier: class_or_decltype class_or_decltype: ["::"] [nested_name_specifier] class_name | decltype_specifier """ bases = [] while True: tok = self.lex.token() tok_type = tok.type # might start with attributes if tok.type in self._attribute_specifier_seq_start_types: self._consume_attribute_specifier_seq(tok) tok = self.lex.token() tok_type = tok.type access = default_access virtual = False parameter_pack = False # virtual/access specifier comes next while tok_type in self._base_access_virtual: if tok_type == "virtual": virtual = True else: access = tok_type tok = self.lex.token() tok_type = tok.type # Followed by the name typename, _ = self._parse_pqname(tok) # And potentially a parameter pack if self.lex.token_if("ELLIPSIS"): parameter_pack = True bases.append(BaseClass(access, typename, virtual, parameter_pack)) if not self.lex.token_if(","): break return bases def _parse_class_decl( self, typename: PQName, tok: LexToken, doxygen: typing.Optional[str], template: TemplateDeclTypeVar, typedef: bool, location: Location, mods: ParsedTypeModifiers, ) -> None: """ class_specifier: class_head "{" [member_specification] "}" class_head: class_key [attribute_specifier_seq] class_head_name [class_virt_specifier_seq] [base_clause] | class_key [attribute_specifier_seq] [base_clause] class_key: "class" | "struct" | "union" class_head_name: [nested_name_specifier] class_name class_name: IDENTIFIER | simple_template_id class_virt_specifier_seq: class_virt_specifier | class_virt_specifier_seq class_virt_specifier class_virt_specifier: "final" | "explicit" """ bases = [] explicit = False final = False default_access = "private" if typename.classkey == "class" else "public" #: entry: token is one of _class_enum_stage2 tok_type = tok.type while True: if tok_type == "final": final = True elif tok_type == "explicit": explicit = True else: break tok = self.lex.token() tok_type = tok.type if tok_type == ":": bases = self._parse_class_decl_base_clause(default_access) tok = self.lex.token() tok_type = tok.type if tok_type != "{": raise self._parse_error(tok, "{") clsdecl = ClassDecl( typename, bases, template, explicit, final, doxygen, self._current_access ) state: ClassBlockState = ClassBlockState( self.state, location, clsdecl, default_access, typedef, mods ) self._setup_state(state) if self.visitor.on_class_start(state) is False: self.visitor = null_visitor def _finish_class_decl(self, state: ClassBlockState) -> None: self._finish_class_or_enum( state.class_decl.typename, state.typedef, state.mods, state.class_decl.classkey, ) def _process_access_specifier( self, tok: LexToken, doxygen: typing.Optional[str] ) -> None: state = self.state if not isinstance(state, ClassBlockState): raise self._parse_error(tok) state._set_access(tok.value) self._next_token_must_be(":") def _discard_ctor_initializer(self) -> None: """ ctor_initializer: ":" mem_initializer_list mem_initializer_list: mem_initializer ["..."] | mem_initializer "," mem_initializer_list ["..."] mem_initializer: mem_initializer_id "(" [expression_list] ")" | mem_initializer_id braced_init_list mem_initializer_id: class_or_decltype | IDENTIFIER """ self.debug_print("discarding ctor intializer") # all of this is discarded.. the challenge is to determine # when the initializer ends and the function starts get_token = self.lex.token while True: tok = get_token() if tok.type == "DBL_COLON": tok = get_token() if tok.type == "decltype": tok = self._next_token_must_be("(") self._consume_balanced_tokens(tok) tok = get_token() # each initializer is either foo() or foo{}, so look for that while True: if tok.type not in ("{", "("): tok = get_token() continue if tok.type == "{": self._discard_contents("{", "}") elif tok.type == "(": self._discard_contents("(", ")") tok = get_token() break # at the end if tok.type == "ELLIPSIS": tok = get_token() if tok.type == ",": continue elif tok.type == "{": # reached the function self._discard_contents("{", "}") return else: raise self._parse_error(tok, ",' or '{") # # Variable parsing # def _parse_bitfield(self) -> int: # is a integral constant expression... for now, just do integers tok = self._next_token_must_be("INT_CONST_DEC") return int(tok.value) def _parse_field( self, mods: ParsedTypeModifiers, dtype: DecoratedType, pqname: typing.Optional[PQName], template: typing.Optional[TemplateDecl], doxygen: typing.Optional[str], location: Location, is_typedef: bool, ) -> None: state = self.state state.location = location if isinstance(state, ClassBlockState): is_class_block = True class_state = state else: is_class_block = False name = None bits = None default = None if not pqname: if is_typedef: raise CxxParseError("empty name not allowed in typedef") if not is_class_block: raise CxxParseError("variables must have names") else: last_segment = pqname.segments[-1] if not isinstance(last_segment, NameSpecifier): raise CxxParseError(f"invalid name for variable: {pqname}") if is_typedef or is_class_block: name = last_segment.name if len(pqname.segments) > 1: raise CxxParseError(f"name cannot have multiple segments: {pqname}") # check for array tok = self.lex.token_if("[") if tok: dtype = self._parse_array_type(tok, dtype) # bitfield tok = self.lex.token_if(":") if tok: if is_typedef or not is_class_block: raise self._parse_error(tok) bits = self._parse_bitfield() # default values tok = self.lex.token_if("=") if tok: if is_typedef: raise self._parse_error(tok) default = self._create_value(self._consume_value_until([], ",", ";")) else: # default value initializer tok = self.lex.token_if("{") if tok: if is_typedef: raise self._parse_error(tok) default = self._create_value(self._consume_balanced_tokens(tok)) if doxygen is None: # try checking after the var doxygen = self.lex.get_doxygen_after() if is_typedef: if not name: raise self._parse_error(None) typedef = Typedef(dtype, name, self._current_access) self.visitor.on_typedef(state, typedef) else: props = dict.fromkeys(mods.both.keys(), True) props.update(dict.fromkeys(mods.vars.keys(), True)) if is_class_block: access = self._current_access assert access is not None f = Field( name=name, type=dtype, access=access, value=default, bits=bits, doxygen=doxygen, **props, ) self.visitor.on_class_field(class_state, f) else: assert pqname is not None v = Variable( pqname, dtype, default, doxygen=doxygen, template=template, **props ) self.visitor.on_variable(state, v) # # PQName parsing # def _parse_pqname_decltype_specifier(self) -> DecltypeSpecifier: """ decltype_specifier: "decltype" "(" expression ")" """ # entry: decltype just consumed tok = self._next_token_must_be("(") toks = self._consume_balanced_tokens(tok)[1:-1] return DecltypeSpecifier([Token(tok.value, tok.type) for tok in toks]) _name_compound_start = {"struct", "enum", "class", "union"} _compound_fundamentals = { "unsigned", "signed", "short", "int", "long", "float", "double", "char", } _fundamentals = _compound_fundamentals | { "bool", "char16_t", "char32_t", "nullptr_t", "wchar_t", "void", } def _parse_pqname_fundamental(self, tok_value: str) -> FundamentalSpecifier: fnames = [tok_value] _compound_fundamentals = self._compound_fundamentals # compound fundamentals can show up in groups in any order # -> TODO: const can show up in here if tok_value in _compound_fundamentals: while True: tok = self.lex.token_if_in_set(_compound_fundamentals) if not tok: break fnames.append(tok.value) return FundamentalSpecifier(" ".join(fnames)) def _parse_pqname_name_operator(self) -> LexTokenList: # last tok was 'operator' -- collect until ( is reached # - no validation done here, we assume the code is valid tok = self.lex.token() parts = [tok] # special case: operator() if tok.value == "(": tok = self._next_token_must_be(")") parts.append(tok) return parts self._consume_until(parts, "(") return parts _pqname_start_tokens = ( { "auto", "decltype", "NAME", "operator", "template", "typename", "DBL_COLON", "final", } | _name_compound_start | _fundamentals ) def _parse_pqname_name( self, tok_value: str ) -> typing.Tuple[NameSpecifier, typing.Optional[str]]: name = "" specialization = None op = None # parse out operators as that's generally useful if tok_value == "operator": op_parts = self._parse_pqname_name_operator() op = "".join(o.value for o in op_parts) name = f"operator{op}" else: name = tok_value if self.lex.token_if("<"): # template specialization specialization = self._parse_template_specialization() return NameSpecifier(name, specialization), op def _parse_pqname( self, tok: typing.Optional[LexToken], *, fn_ok: bool = False, compound_ok: bool = False, fund_ok: bool = False, ) -> typing.Tuple[PQName, typing.Optional[str]]: """ Parses a possibly qualified function name or a type name, returns when unexpected item encountered (but does not consume it) :param fn_ok: Operator functions ok :param compound_ok: Compound types ok :param fund_ok: Fundamental types ok qualified_id: ["::"] nested_name_specifier ["template"] unqualified_id | "::" IDENTIFIER | "::" operator_function_id | "::" literal_operator_id | "::" template_id unqualified_id: IDENTIFIER | operator_function_id | conversion_function_id | literal_operator_id | "~" class_name | "~" decltype_specifier | template_id conversion_function_id: "operator" conversion_type_id conversion_type_id: type_specifier_seq [conversion_declarator] conversion_declarator: ptr_operator [conversion_declarator] nested_name_specifier: type_name "::" | IDENTIFIER "::" | decltype_specifier "::" | nested_name_specifier IDENTIFIER "::" | nested_name_specifier ["template"] simple_template_id "::" type_name: IDENTIFIER | simple_template_id """ classkey = None segments: typing.List[PQNameSegment] = [] op = None has_typename = False if tok is None: tok = self.lex.token() if tok.type not in self._pqname_start_tokens: raise self._parse_error(tok) if tok.type == "auto": return PQName([AutoSpecifier()]), None _fundamentals = self._fundamentals # The pqname could start with class/enum/struct/union if tok.value in self._name_compound_start: if not compound_ok: raise self._parse_error(tok) classkey = tok.value if classkey == "enum": # check for enum class/struct tok = self.lex.token_if("class", "struct") if tok: classkey = f"{classkey} {tok.value}" # Sometimes there's an embedded attribute tok = self.lex.token_if(*self._attribute_start_tokens) if tok: self._consume_attribute(tok) tok = self.lex.token_if("NAME", "DBL_COLON") if not tok: # Handle unnamed class/enum/struct self.anon_id += 1 segments.append(AnonymousName(self.anon_id)) return PQName(segments, classkey), None elif tok.type == "typename": has_typename = True tok = self.lex.token() if tok.type not in self._pqname_start_tokens: raise self._parse_error(tok) # First section of the name: Add empty segment if starting out with a # namespace specifier if tok.type == "DBL_COLON": segments.append(NameSpecifier("")) tok = self._next_token_must_be("NAME", "template", "operator") while True: tok_value = tok.value if tok_value == "decltype": segments.append(self._parse_pqname_decltype_specifier()) # check for fundamentals, consume them elif tok_value in _fundamentals: if not fund_ok: raise self._parse_error(tok) segments.append(self._parse_pqname_fundamental(tok_value)) # no additional parts after fundamentals break else: if tok_value == "[[": self._consume_attribute_specifier_seq(tok) if tok_value == "template": tok = self._next_token_must_be("NAME") tok_value = tok.value name, op = self._parse_pqname_name(tok_value) segments.append(name) if op: if not fn_ok: # encountered unexpected operator raise self._parse_error(tok, "NAME") # nothing after operator break # If no more segments, we're done if not self.lex.token_if("DBL_COLON"): break tok = self._next_token_must_be("NAME", "operator", "template", "decltype") pqname = PQName(segments, classkey, has_typename) self.debug_print( "parse_pqname: %s op=%s", pqname, op, ) return pqname, op # # Function parsing # def _parse_parameter( self, tok: typing.Optional[LexToken], cls: typing.Type[PT], concept_ok: bool, end: str = ")", ) -> typing.Tuple[PT, typing.Optional[Type]]: """ Parses a single parameter (excluding vararg parameters). Also used to parse template non-type parameters Returns parameter type, abbreviated template type """ param_name = None default = None param_pack = False parsed_type: typing.Optional[Type] at_type: typing.Optional[Type] = None if not tok: tok = self.lex.token() # placeholder type, skip typename if tok.type == "auto": at_type = parsed_type = Type(PQName([AutoSpecifier()])) else: # required typename + decorators parsed_type, mods = self._parse_type(tok) if parsed_type is None: raise self._parse_error(None) mods.validate(var_ok=False, meth_ok=False, msg="parsing parameter") # Could be a concept if concept_ok and self.lex.token_if("auto"): at_type = Type(parsed_type.typename) parsed_type.typename = PQName([AutoSpecifier()]) dtype = self._parse_cv_ptr(parsed_type) # optional parameter pack if self.lex.token_if("ELLIPSIS"): param_pack = True # name can be surrounded by parens tok = self.lex.token_if("(") if tok: toks = self._consume_balanced_tokens(tok) self.lex.return_tokens(toks[1:-1]) # optional name tok = self.lex.token_if("NAME", "final") if tok: param_name = tok.value # optional array parameter tok = self.lex.token_if("[") if tok: dtype = self._parse_array_type(tok, dtype) # optional default value if self.lex.token_if("="): default = self._create_value(self._consume_value_until([], ",", end)) # abbreviated template pack if at_type and self.lex.token_if("ELLIPSIS"): param_pack = True param = cls(type=dtype, name=param_name, default=default, param_pack=param_pack) self.debug_print("parameter: %s", param) return param, at_type def _parse_parameters( self, concept_ok: bool ) -> typing.Tuple[typing.List[Parameter], bool, typing.List[TemplateParam]]: """ Consumes function parameters and returns them, and vararg if found, and promotes abbreviated template parameters to actual template parameters if concept_ok is True """ # starting at a ( # special case: zero parameters if self.lex.token_if(")"): return [], False, [] params: typing.List[Parameter] = [] vararg = False at_params: typing.List[TemplateParam] = [] while True: if self.lex.token_if("ELLIPSIS"): vararg = True self._next_token_must_be(")") break param, at_type = self._parse_parameter(None, Parameter, concept_ok) params.append(param) if at_type: at_params.append( TemplateNonTypeParam( type=at_type, param_idx=len(params) - 1, param_pack=param.param_pack, ) ) tok = self._next_token_must_be(",", ")") if tok.value == ")": break # convert fn(void) to fn() if self.options.convert_void_to_zero_params and len(params) == 1: p0_type = params[0].type if ( isinstance(p0_type, Type) and len(p0_type.typename.segments) == 1 and getattr(p0_type.typename.segments[0], "name", None) == "void" ): params = [] return params, vararg, at_params _auto_return_typename = PQName([AutoSpecifier()]) def _parse_trailing_return_type( self, return_type: typing.Optional[DecoratedType] ) -> DecoratedType: # entry is "->" if not ( isinstance(return_type, Type) and not return_type.const and not return_type.volatile and return_type.typename == self._auto_return_typename ): raise CxxParseError( f"function with trailing return type must specify return type of 'auto', not {return_type}" ) parsed_type, mods = self._parse_type(None) if parsed_type is None: raise self._parse_error(None) mods.validate(var_ok=False, meth_ok=False, msg="parsing trailing return type") dtype = self._parse_cv_ptr(parsed_type) return dtype def _parse_fn_end(self, fn: Function) -> None: """ Consumes the various keywords after the parameters in a function declaration, and definition if present. """ if self.lex.token_if("throw"): tok = self._next_token_must_be("(") fn.throw = self._create_value(self._consume_balanced_tokens(tok)[1:-1]) elif self.lex.token_if("noexcept"): toks = [] otok = self.lex.token_if("(") if otok: toks = self._consume_balanced_tokens(otok)[1:-1] fn.noexcept = self._create_value(toks) else: rtok = self.lex.token_if("requires") if rtok: # requires on a function must always be accompanied by a template if fn.template is None: raise self._parse_error(rtok) fn.raw_requires = self._parse_requires(rtok) if self.lex.token_if("ARROW"): return_type = self._parse_trailing_return_type(fn.return_type) fn.has_trailing_return = True fn.return_type = return_type if self.lex.token_if("{"): self._discard_contents("{", "}") fn.has_body = True def _parse_method_end(self, method: Method) -> None: """ Consumes the various keywords after the parameters in a method declaration, and definition if present. """ # various keywords at the end of a method get_token = self.lex.token while True: tok = get_token() tok_value = tok.value if tok_value in (":", "{"): method.has_body = True if tok_value == ":": self._discard_ctor_initializer() elif tok_value == "{": self._discard_contents("{", "}") break elif tok_value == "=": tok = get_token() tok_value = tok.value if tok_value == "0": method.pure_virtual = True elif tok_value == "delete": method.deleted = True elif tok_value == "default": method.default = True else: raise self._parse_error(tok, "0/delete/default") break elif tok_value in ("const", "volatile", "override", "final"): setattr(method, tok_value, True) elif tok_value in ("&", "&&"): method.ref_qualifier = tok_value elif tok_value == "->": return_type = self._parse_trailing_return_type(method.return_type) method.has_trailing_return = True method.return_type = return_type if self.lex.token_if("{"): self._discard_contents("{", "}") method.has_body = True break elif tok_value == "throw": tok = self._next_token_must_be("(") method.throw = self._create_value(self._consume_balanced_tokens(tok)) elif tok_value == "noexcept": toks = [] otok = self.lex.token_if("(") if otok: toks = self._consume_balanced_tokens(otok)[1:-1] method.noexcept = self._create_value(toks) elif tok_value == "requires": method.raw_requires = self._parse_requires(tok) else: self.lex.return_token(tok) break def _parse_function( self, mods: ParsedTypeModifiers, return_type: typing.Optional[DecoratedType], pqname: PQName, op: typing.Optional[str], template: TemplateDeclTypeVar, doxygen: typing.Optional[str], location: Location, constructor: bool, destructor: bool, is_friend: bool, is_typedef: bool, msvc_convention: typing.Optional[LexToken], is_guide: bool = False, ) -> bool: """ Assumes the caller has already consumed the return type and name, this consumes the rest of the function including the definition if present Returns True if function has a body and it was consumed """ # TODO: explicit (operator int)() in C++20 # last segment must be NameSpecifier if not isinstance(pqname.segments[-1], NameSpecifier): raise self._parse_error(None) props: typing.Dict props = dict.fromkeys(mods.both.keys(), True) if msvc_convention: props["msvc_convention"] = msvc_convention.value state = self.state state.location = location is_class_block = isinstance(state, ClassBlockState) params, vararg, at_params = self._parse_parameters(True) # Promote abbreviated template parameters if at_params: if template is None: template = TemplateDecl(at_params) elif isinstance(template, TemplateDecl): template.params.extend(at_params) else: template[-1].params.extend(at_params) # A method outside of a class has multiple name segments multiple_name_segments = len(pqname.segments) > 1 if (is_class_block or multiple_name_segments) and not is_typedef: props.update(dict.fromkeys(mods.meths.keys(), True)) method = Method( return_type, pqname, params, vararg, doxygen=doxygen, constructor=constructor, destructor=destructor, template=template, operator=op, access=self._current_access, **props, # type: ignore ) self._parse_method_end(method) if is_class_block: assert isinstance(state, ClassBlockState) if is_friend: friend = FriendDecl(fn=method) self.visitor.on_class_friend(state, friend) else: # method must not have multiple segments except for operator if len(pqname.segments) > 1: if getattr(pqname.segments[0], "name", None) != "operator": raise self._parse_error(None) self.visitor.on_class_method(state, method) else: assert isinstance(state, (ExternBlockState, NamespaceBlockState)) # only template specializations can be declared without a body here if not method.has_body and not method.template: raise self._parse_error(None, expected="Method body") self.visitor.on_method_impl(state, method) return method.has_body or method.has_trailing_return elif is_guide: assert isinstance(state, (ExternBlockState, NamespaceBlockState)) if not self.lex.token_if("ARROW"): raise self._parse_error(None, expected="Trailing return type") return_type = self._parse_trailing_return_type( Type(PQName([AutoSpecifier()])) ) guide = DeductionGuide( return_type, name=pqname, parameters=params, doxygen=doxygen, ) self.visitor.on_deduction_guide(state, guide) return False else: assert return_type is not None fn = Function( return_type, pqname, params, vararg, doxygen=doxygen, template=template, operator=op, **props, ) self._parse_fn_end(fn) if is_typedef: if len(pqname.segments) != 1: raise CxxParseError( "typedef name may not be a nested-name-specifier" ) name: typing.Optional[str] = getattr(pqname.segments[0], "name", None) if not name: raise CxxParseError("typedef function must have a name") if fn.constexpr: raise CxxParseError("typedef function may not be constexpr") if fn.extern: raise CxxParseError("typedef function may not be extern") if fn.static: raise CxxParseError("typedef function may not be static") if fn.inline: raise CxxParseError("typedef function may not be inline") if fn.has_body: raise CxxParseError("typedef may not be a function definition") if fn.template: raise CxxParseError("typedef function may not have a template") return_type = fn.return_type if return_type is None: raise CxxParseError("typedef function must have return type") fntype = FunctionType( return_type, fn.parameters, fn.vararg, fn.has_trailing_return, noexcept=fn.noexcept, msvc_convention=fn.msvc_convention, ) typedef = Typedef(fntype, name, self._current_access) self.visitor.on_typedef(state, typedef) return False else: if not isinstance(state, (ExternBlockState, NamespaceBlockState)): raise CxxParseError("internal error") self.visitor.on_function(state, fn) return fn.has_body or fn.has_trailing_return # # Decorated type parsing # def _parse_array_type(self, tok: LexToken, dtype: DecoratedType) -> Array: assert tok.type == "[" if isinstance(dtype, (Reference, MoveReference)): raise CxxParseError("arrays of references are illegal", tok) toks = self._consume_balanced_tokens(tok) otok = self.lex.token_if("[") if otok: # recurses because array types are right to left dtype = self._parse_array_type(otok, dtype) toks = toks[1:-1] size = None if toks: size = self._create_value(toks) return Array(dtype, size) def _parse_cv_ptr( self, dtype: DecoratedType, ) -> DecoratedType: dtype_or_fn = self._parse_cv_ptr_or_fn(dtype) if isinstance(dtype_or_fn, FunctionType): raise CxxParseError("unexpected function type") return dtype_or_fn def _parse_cv_ptr_or_fn( self, dtype: typing.Union[ Array, Pointer, MoveReference, Reference, Type, FunctionType ], nonptr_fn: bool = False, ) -> typing.Union[Array, Pointer, MoveReference, Reference, Type, FunctionType]: # nonptr_fn is for parsing function types directly in template specialization while True: tok = self.lex.token_if("*", "const", "volatile", "(") if not tok: break if tok.type == "*": if isinstance(dtype, (Reference, MoveReference)): raise self._parse_error(tok) dtype = Pointer(dtype) elif tok.type == "const": if not isinstance(dtype, (Pointer, Type)): raise self._parse_error(tok) dtype.const = True elif tok.type == "volatile": if not isinstance(dtype, (Pointer, Type)): raise self._parse_error(tok) dtype.volatile = True elif nonptr_fn: # remove any inner grouping parens while True: gtok = self.lex.token_if("(") if not gtok: break toks = self._consume_balanced_tokens(gtok) self.lex.return_tokens(toks[1:-1]) fn_params, vararg, _ = self._parse_parameters(False) assert not isinstance(dtype, FunctionType) dtype = dtype_fn = FunctionType(dtype, fn_params, vararg) if self.lex.token_if("ARROW"): return_type = self._parse_trailing_return_type(dtype_fn.return_type) dtype_fn.has_trailing_return = True dtype_fn.return_type = return_type else: msvc_convention = None msvc_convention_tok = self.lex.token_if_val(*self._msvc_conventions) if msvc_convention_tok: msvc_convention = msvc_convention_tok.value # Check to see if this is a grouping paren or something else if not self.lex.token_peek_if("*", "&"): self.lex.return_token(tok) break # this is a grouping paren, so consume it toks = self._consume_balanced_tokens(tok) # Now check to see if we have either an array or a function pointer aptok = self.lex.token_if("[", "(") if aptok: if aptok.type == "[": assert not isinstance(dtype, FunctionType) dtype = self._parse_array_type(aptok, dtype) elif aptok.type == "(": fn_params, vararg, _ = self._parse_parameters(False) # the type we already have is the return type of the function pointer assert not isinstance(dtype, FunctionType) dtype = FunctionType( dtype, fn_params, vararg, msvc_convention=msvc_convention ) # the inner tokens must either be a * or a pqname that ends # with ::* (member function pointer) # ... TODO member function pointer :( # return the inner toks and recurse # -> this could return some weird results for invalid code, but # we don't support that anyways so it's fine? self.lex.return_tokens(toks[1:-1]) dtype = self._parse_cv_ptr_or_fn(dtype, nonptr_fn) break tok = self.lex.token_if("&", "DBL_AMP") if tok: assert not isinstance(dtype, (Reference, MoveReference)) if tok.type == "&": dtype = Reference(dtype) else: dtype = MoveReference(dtype) # peek at the next token and see if it's a paren. If so, it might # be a nasty function pointer if self.lex.token_peek_if("("): dtype = self._parse_cv_ptr_or_fn(dtype, nonptr_fn) return dtype # Applies to variables and return values _type_kwd_both = {"const", "constexpr", "extern", "inline", "static"} # Only found on methods _type_kwd_meth = {"explicit", "virtual"} _parse_type_ptr_ref_paren = {"*", "&", "DBL_AMP", "("} def _parse_type( self, tok: typing.Optional[LexToken], operator_ok: bool = False, ) -> typing.Tuple[typing.Optional[Type], ParsedTypeModifiers]: """ This parses a typename and stops parsing when it hits something that it doesn't understand. The caller uses the results to figure out what got parsed This only parses the base type, does not parse pointers, references, or additional const/volatile qualifiers The returned type will only be None if operator_ok is True and an operator is encountered. """ const = False volatile = False # Modifiers that apply to the variable/function # -> key is name of modifier, value is a token so that we can emit an # appropriate error vars: typing.Dict[str, LexToken] = {} # only found on variables both: typing.Dict[str, LexToken] = {} # found on either meths: typing.Dict[str, LexToken] = {} # only found on methods get_token = self.lex.token if not tok: tok = get_token() pqname: typing.Optional[PQName] = None pqname_optional = False _pqname_start_tokens = self._pqname_start_tokens _attribute_start = self._attribute_start_tokens # This loop parses until it finds two pqname or ptr/ref while True: tok_type = tok.type if tok_type in _pqname_start_tokens: if pqname is not None: # found second set of names, done here break if operator_ok and tok_type == "operator": # special case: conversion operators such as operator bool pqname_optional = True break pqname, _ = self._parse_pqname( tok, compound_ok=True, fn_ok=False, fund_ok=True ) elif tok_type in self._parse_type_ptr_ref_paren: if pqname is None: raise self._parse_error(tok) break elif tok_type == "const": const = True elif tok_type in self._type_kwd_both: if tok_type == "extern": # TODO: store linkage self.lex.token_if("STRING_LITERAL") both[tok_type] = tok elif tok_type in self._type_kwd_meth: meths[tok_type] = tok elif tok_type == "mutable": vars["mutable"] = tok elif tok_type == "volatile": volatile = True elif tok_type in _attribute_start: self._consume_attribute(tok) else: break tok = get_token() if pqname is None: if not pqname_optional: raise self._parse_error(tok) parsed_type = None else: # Construct a type from the parsed name parsed_type = Type(pqname, const, volatile) self.lex.return_token(tok) # Always return the modifiers mods = ParsedTypeModifiers(vars, both, meths) return parsed_type, mods def _parse_decl( self, parsed_type: Type, mods: ParsedTypeModifiers, location: Location, doxygen: typing.Optional[str], template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, ) -> bool: toks = [] # On entry we only have the base type, decorate it dtype: typing.Optional[DecoratedType] dtype = self._parse_cv_ptr(parsed_type) state = self.state pqname = None constructor = False destructor = False op = None msvc_convention = None is_guide = False # If we have a leading (, that's either an obnoxious grouping # paren or it's a constructor tok = self.lex.token_if("(") if tok: dsegments: typing.List[PQNameSegment] = [] if isinstance(dtype, Type): dsegments = dtype.typename.segments # Check to see if this is a constructor/destructor by matching # the method name to the class name is_class_block = isinstance(state, ClassBlockState) if (is_class_block or len(dsegments) > 1) and isinstance(dtype, Type): if not is_class_block: # must be an instance of a class cls_name = getattr(dsegments[-2], "name", None) elif not is_friend: assert isinstance(state, ClassBlockState) # class name to match against is this class cls_name = getattr( state.class_decl.typename.segments[-1], "name", None ) else: # class name to match against is the other class if len(dsegments) >= 2: cls_name = getattr(dsegments[-2], "name", None) else: cls_name = None ret_name = getattr(dsegments[-1], "name", None) if cls_name: if cls_name == ret_name: pqname = dtype.typename dtype = None constructor = True self.lex.return_token(tok) elif f"~{cls_name}" == ret_name: pqname = dtype.typename dtype = None destructor = True self.lex.return_token(tok) if dtype: # if it's not a constructor/destructor, it could be a # grouping paren like "void (name(int x));" toks = self._consume_balanced_tokens(tok) # check to see if the next token is an arrow, and thus a trailing return if self.lex.token_peek_if("ARROW"): self.lex.return_tokens(toks) # the leading name of the class/ctor has been parsed as a type before the parens pqname = parsed_type.typename is_guide = True else: # .. not sure what it's grouping, so put it back? self.lex.return_tokens(toks[1:-1]) if dtype: msvc_convention = self.lex.token_if_val(*self._msvc_conventions) tok = self.lex.token_if_in_set(self._pqname_start_tokens) if tok: pqname, op = self._parse_pqname(tok, fn_ok=True) # TODO: "type fn(x);" is ambiguous here. Because this is a header # parser, we assume it's a function, not a variable declaration # calling a constructor # if ( then it's a function/method if self.lex.token_if("("): if not pqname: raise self._parse_error(None) return self._parse_function( mods, dtype, pqname, op, template, doxygen, location, constructor, destructor, is_friend, is_typedef, msvc_convention, is_guide, ) elif msvc_convention: raise self._parse_error(msvc_convention) # anything else is a field/variable if is_friend: # "friend Foo;" if not self.lex.token_if(";"): raise self._parse_error(None) assert isinstance(state, ClassBlockState) fwd = ForwardDecl( parsed_type.typename, template, doxygen, access=self._current_access, ) friend = FriendDecl(cls=fwd) state.location = location self.visitor.on_class_friend(state, friend) return True if op: raise self._parse_error(None) if not dtype: raise CxxParseError("appear to be parsing a field without a type") if isinstance(template, list): raise CxxParseError("multiple template declarations on a field") self._parse_field(mods, dtype, pqname, template, doxygen, location, is_typedef) return False def _parse_operator_conversion( self, mods: ParsedTypeModifiers, location: Location, doxygen: typing.Optional[str], template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, ) -> None: tok = self._next_token_must_be("operator") if is_typedef: raise self._parse_error(tok, "operator not permitted in typedef") # next piece must be the conversion type ctype, cmods = self._parse_type(None) if ctype is None: raise self._parse_error(None) cmods.validate(var_ok=False, meth_ok=False, msg="parsing conversion operator") # Check for any cv decorations for the type rtype = self._parse_cv_ptr(ctype) # then this must be a method self._next_token_must_be("(") # make our own pqname/op here segments: typing.List[PQNameSegment] = [NameSpecifier("operator")] pqname = PQName(segments) op = "conversion" if self._parse_function( mods, rtype, pqname, op, template, doxygen, location, False, False, is_friend, False, None, ): # has function body and handled it return # if this is just a declaration, next token should be ; self._next_token_must_be(";") _class_enum_stage2 = {":", "final", "explicit", "{"} def _parse_declarations( self, tok: LexToken, doxygen: typing.Optional[str], template: TemplateDeclTypeVar = None, is_typedef: bool = False, is_friend: bool = False, ) -> None: """ Parses a sequence of declarations """ # On entry to this function, we don't have enough context to know # what we're going to find, so keep trying to deduce it location = tok.location # Almost always starts out with some kind of type name or a modifier parsed_type, mods = self._parse_type(tok, operator_ok=True) # Check to see if this might be a class/enum declaration if ( parsed_type is not None and parsed_type.typename.classkey and self._maybe_parse_class_enum_decl( parsed_type, mods, doxygen, template, is_typedef, is_friend, location ) ): return var_ok = True if is_typedef: var_ok = False meth_ok = False msg = "parsing typedef" elif isinstance(self.state, ClassBlockState): meth_ok = True msg = "parsing declaration in class" else: meth_ok = False msg = "parsing declaration" mods.validate(var_ok=var_ok, meth_ok=meth_ok, msg=msg) if parsed_type is None: # this means an operator was encountered, deal with the special case self._parse_operator_conversion( mods, location, doxygen, template, is_typedef, is_friend ) return # Ok, dealing with a variable or function/method while True: if self._parse_decl( parsed_type, mods, location, doxygen, template, is_typedef, is_friend ): # if it returns True then it handled the end of the statement break # Unset the doxygen, location doxygen = None # Check for multiple declarations tok = self._next_token_must_be(",", ";") location = tok.location if tok.type == ";": break def _maybe_parse_class_enum_decl( self, parsed_type: Type, mods: ParsedTypeModifiers, doxygen: typing.Optional[str], template: TemplateDeclTypeVar, is_typedef: bool, is_friend: bool, location: Location, ) -> bool: # check for forward declaration or friend declaration if self.lex.token_if(";"): if is_typedef: raise self._parse_error(None) classkey = parsed_type.typename.classkey mods.validate( var_ok=False, meth_ok=False, msg=f"parsing {classkey} forward declaration", ) # enum cannot be forward declared, but "enum class" can # -> but `friend enum X` is fine if not classkey: raise self._parse_error(None) elif classkey == "enum" and not is_friend: raise self._parse_error(None) elif template and classkey.startswith("enum"): # enum class cannot have a template raise self._parse_error(None) fdecl = ForwardDecl( parsed_type.typename, template, doxygen, access=self._current_access ) state = self.state state.location = location if is_friend: assert isinstance(state, ClassBlockState) friend = FriendDecl(cls=fdecl) self.visitor.on_class_friend(state, friend) else: self.visitor.on_forward_decl(state, fdecl) return True tok = self.lex.token_if_in_set(self._class_enum_stage2) if tok: classkey = parsed_type.typename.classkey # var is ok because it could be carried on to any variables mods.validate( var_ok=not is_typedef, meth_ok=False, msg=f"parsing {classkey} declaration", ) typename = parsed_type.typename if is_friend: # friend declaration doesn't have extra context raise self._parse_error(tok) if typename.classkey in ("class", "struct", "union"): self._parse_class_decl( typename, tok, doxygen, template, is_typedef, location, mods ) else: if template: # enum cannot have a template raise self._parse_error(tok) self._parse_enum_decl( typename, tok, doxygen, is_typedef, location, mods ) return True # Otherwise this must be a variable or function return False def _finish_class_or_enum( self, name: PQName, is_typedef: bool, mods: ParsedTypeModifiers, classkey: typing.Optional[str], ) -> None: parsed_type = Type(name) tok = self.lex.token_if("__attribute__") if tok: self._consume_gcc_attribute(tok) if not is_typedef and self.lex.token_if(";"): # if parent scope is a class, add the anonymous # union or struct to the parent fields if isinstance(self.state, ClassBlockState): class_state = self.state access = self._current_access assert access is not None if ( classkey is not None and classkey in ["union", "struct"] and isinstance(name.segments[-1], AnonymousName) ): f = Field( type=Type(name), access=access, ) self.visitor.on_class_field(class_state, f) return while True: location = self.lex.current_location() if self._parse_decl( parsed_type, mods, location, None, None, is_typedef, False ): # if it returns True then it handled the end of the statement break # Check for multiple declarations tok = self._next_token_must_be(",", ";") if tok.type == ";": break cxxheaderparser-1.3.1/cxxheaderparser/parserstate.py000066400000000000000000000074021455035044000230060ustar00rootroot00000000000000import typing if typing.TYPE_CHECKING: from .visitor import CxxVisitor # pragma: nocover from .errors import CxxParseError from .lexer import LexToken, Location from .types import ClassDecl, NamespaceDecl class ParsedTypeModifiers(typing.NamedTuple): vars: typing.Dict[str, LexToken] # only found on variables both: typing.Dict[str, LexToken] # found on either variables or functions meths: typing.Dict[str, LexToken] # only found on methods def validate(self, *, var_ok: bool, meth_ok: bool, msg: str) -> None: # Almost there! Do any checks the caller asked for if not var_ok and self.vars: for tok in self.vars.values(): raise CxxParseError(f"{msg}: unexpected '{tok.value}'") if not meth_ok and self.meths: for tok in self.meths.values(): raise CxxParseError(f"{msg}: unexpected '{tok.value}'") if not meth_ok and not var_ok and self.both: for tok in self.both.values(): raise CxxParseError(f"{msg}: unexpected '{tok.value}'") #: custom user data for this state type T = typing.TypeVar("T") #: type of custom user data for a parent state PT = typing.TypeVar("PT") class BaseState(typing.Generic[T, PT]): #: Uninitialized user data available for use by visitor implementations. You #: should set this in a ``*_start`` method. user_data: T #: parent state parent: typing.Optional["State"] #: Approximate location that the parsed element was found at location: Location #: internal detail used by parser _prior_visitor: "CxxVisitor" def __init__(self, parent: typing.Optional["State"], location: Location) -> None: self.parent = parent self.location = location def _finish(self, visitor: "CxxVisitor") -> None: pass class ExternBlockState(BaseState[T, PT]): parent: "NonClassBlockState" #: The linkage for this extern block linkage: str def __init__( self, parent: "NonClassBlockState", location: Location, linkage: str ) -> None: super().__init__(parent, location) self.linkage = linkage def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_extern_block_end(self) class NamespaceBlockState(BaseState[T, PT]): parent: "NonClassBlockState" #: The incremental namespace for this block namespace: NamespaceDecl def __init__( self, parent: typing.Optional["NonClassBlockState"], location: Location, namespace: NamespaceDecl, ) -> None: super().__init__(parent, location) self.namespace = namespace def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_namespace_end(self) class ClassBlockState(BaseState[T, PT]): parent: "State" #: class decl block being processed class_decl: ClassDecl #: Current access level for items encountered access: str #: Currently parsing as a typedef typedef: bool #: modifiers to apply to following variables mods: ParsedTypeModifiers def __init__( self, parent: typing.Optional["State"], location: Location, class_decl: ClassDecl, access: str, typedef: bool, mods: ParsedTypeModifiers, ) -> None: super().__init__(parent, location) self.class_decl = class_decl self.access = access self.typedef = typedef self.mods = mods def _set_access(self, access: str) -> None: self.access = access def _finish(self, visitor: "CxxVisitor") -> None: visitor.on_class_end(self) State = typing.Union[ NamespaceBlockState[T, PT], ExternBlockState[T, PT], ClassBlockState[T, PT] ] NonClassBlockState = typing.Union[ExternBlockState[T, PT], NamespaceBlockState[T, PT]] cxxheaderparser-1.3.1/cxxheaderparser/preprocessor.py000066400000000000000000000240111455035044000231720ustar00rootroot00000000000000""" Contains optional preprocessor support functions """ import io import re import os import subprocess import sys import tempfile import typing from .options import PreprocessorFunction class PreprocessorError(Exception): pass # # GCC preprocessor support # def _gcc_filter(fname: str, fp: typing.TextIO) -> str: new_output = io.StringIO() keep = True fname = fname.replace("\\", "\\\\") for line in fp: if line.startswith("# "): last_quote = line.rfind('"') if last_quote != -1: keep = line[:last_quote].endswith(fname) if keep: new_output.write(line) new_output.seek(0) return new_output.read() def make_gcc_preprocessor( *, defines: typing.List[str] = [], include_paths: typing.List[str] = [], retain_all_content: bool = False, encoding: typing.Optional[str] = None, gcc_args: typing.List[str] = ["g++"], print_cmd: bool = True, ) -> PreprocessorFunction: """ Creates a preprocessor function that uses g++ to preprocess the input text. gcc is a high performance and accurate precompiler, but if an #include directive can't be resolved or other oddity exists in your input it will throw an error. :param defines: list of #define macros specified as "key value" :param include_paths: list of directories to search for included files :param retain_all_content: If False, only the parsed file content will be retained :param encoding: If specified any include files are opened with this encoding :param gcc_args: This is the path to G++ and any extra args you might want :param print_cmd: Prints the gcc command as its executed .. code-block:: python pp = make_gcc_preprocessor() options = ParserOptions(preprocessor=pp) parse_file(content, options=options) """ if not encoding: encoding = "utf-8" def _preprocess_file(filename: str, content: typing.Optional[str]) -> str: cmd = gcc_args + ["-w", "-E", "-C"] for p in include_paths: cmd.append(f"-I{p}") for d in defines: cmd.append(f"-D{d.replace(' ', '=')}") kwargs = {"encoding": encoding} if filename == "": cmd.append("-") filename = "" if content is None: raise PreprocessorError("no content specified for stdin") kwargs["input"] = content else: cmd.append(filename) if print_cmd: print("+", " ".join(cmd), file=sys.stderr) result: str = subprocess.check_output(cmd, **kwargs) # type: ignore if not retain_all_content: result = _gcc_filter(filename, io.StringIO(result)) return result return _preprocess_file # # Microsoft Visual Studio preprocessor support # def _msvc_filter(fp: typing.TextIO) -> str: # MSVC outputs the original file as the very first #line directive # so we just use that new_output = io.StringIO() keep = True first = fp.readline() assert first.startswith("#line") fname = first[first.find('"') :] for line in fp: if line.startswith("#line"): keep = line.endswith(fname) if keep: new_output.write(line) new_output.seek(0) return new_output.read() def make_msvc_preprocessor( *, defines: typing.List[str] = [], include_paths: typing.List[str] = [], retain_all_content: bool = False, encoding: typing.Optional[str] = None, msvc_args: typing.List[str] = ["cl.exe"], print_cmd: bool = True, ) -> PreprocessorFunction: """ Creates a preprocessor function that uses cl.exe from Microsoft Visual Studio to preprocess the input text. cl.exe is not typically on the path, so you may need to open the correct developer tools shell or pass in the correct path to cl.exe in the `msvc_args` parameter. cl.exe will throw an error if a file referenced by an #include directive is not found. :param defines: list of #define macros specified as "key value" :param include_paths: list of directories to search for included files :param retain_all_content: If False, only the parsed file content will be retained :param encoding: If specified any include files are opened with this encoding :param msvc_args: This is the path to cl.exe and any extra args you might want :param print_cmd: Prints the command as its executed .. code-block:: python pp = make_msvc_preprocessor() options = ParserOptions(preprocessor=pp) parse_file(content, options=options) """ if not encoding: encoding = "utf-8" def _preprocess_file(filename: str, content: typing.Optional[str]) -> str: cmd = msvc_args + ["/nologo", "/E", "/C"] for p in include_paths: cmd.append(f"/I{p}") for d in defines: cmd.append(f"/D{d.replace(' ', '=')}") tfpname = None try: kwargs = {"encoding": encoding} if filename == "": if content is None: raise PreprocessorError("no content specified for stdin") tfp = tempfile.NamedTemporaryFile( mode="w", encoding=encoding, suffix=".h", delete=False ) tfpname = tfp.name tfp.write(content) tfp.close() cmd.append(tfpname) else: cmd.append(filename) if print_cmd: print("+", " ".join(cmd), file=sys.stderr) result: str = subprocess.check_output(cmd, **kwargs) # type: ignore if not retain_all_content: result = _msvc_filter(io.StringIO(result)) finally: if tfpname: os.unlink(tfpname) return result return _preprocess_file # # PCPP preprocessor support (not installed by default) # try: import pcpp from pcpp import Preprocessor, OutputDirective, Action class _CustomPreprocessor(Preprocessor): def __init__( self, encoding: typing.Optional[str], passthru_includes: typing.Optional["re.Pattern"], ): Preprocessor.__init__(self) self.errors: typing.List[str] = [] self.assume_encoding = encoding self.passthru_includes = passthru_includes def on_error(self, file, line, msg): self.errors.append(f"{file}:{line} error: {msg}") def on_include_not_found(self, *ignored): raise OutputDirective(Action.IgnoreAndPassThrough) def on_comment(self, *ignored): return True except ImportError: pcpp = None def _pcpp_filter(fname: str, fp: typing.TextIO) -> str: # the output of pcpp includes the contents of all the included files, which # isn't what a typical user of cxxheaderparser would want, so we strip out # the line directives and any content that isn't in our original file line_ending = f'{fname}"\n' new_output = io.StringIO() keep = True for line in fp: if line.startswith("#line"): keep = line.endswith(line_ending) if keep: new_output.write(line) new_output.seek(0) return new_output.read() def make_pcpp_preprocessor( *, defines: typing.List[str] = [], include_paths: typing.List[str] = [], retain_all_content: bool = False, encoding: typing.Optional[str] = None, passthru_includes: typing.Optional["re.Pattern"] = None, ) -> PreprocessorFunction: """ Creates a preprocessor function that uses pcpp (which must be installed separately) to preprocess the input text. If missing #include files are encountered, this preprocessor will ignore the error. This preprocessor is pure python so it's very portable, and is a good choice if performance isn't critical. :param defines: list of #define macros specified as "key value" :param include_paths: list of directories to search for included files :param retain_all_content: If False, only the parsed file content will be retained :param encoding: If specified any include files are opened with this encoding :param passthru_includes: If specified any #include directives that match the compiled regex pattern will be part of the output. .. code-block:: python pp = make_pcpp_preprocessor() options = ParserOptions(preprocessor=pp) parse_file(content, options=options) """ if pcpp is None: raise PreprocessorError("pcpp is not installed") def _preprocess_file(filename: str, content: typing.Optional[str]) -> str: pp = _CustomPreprocessor(encoding, passthru_includes) if include_paths: for p in include_paths: pp.add_path(p) for define in defines: pp.define(define) if not retain_all_content: pp.line_directive = "#line" if content is None: with open(filename, "r", encoding=encoding) as fp: content = fp.read() pp.parse(content, filename) if pp.errors: raise PreprocessorError("\n".join(pp.errors)) elif pp.return_code: raise PreprocessorError("failed with exit code %d" % pp.return_code) fp = io.StringIO() pp.write(fp) fp.seek(0) if retain_all_content: return fp.read() else: # pcpp emits the #line directive using the filename you pass in # but will rewrite it if it's on the include path it uses. This # is copied from pcpp: abssource = os.path.abspath(filename) for rewrite in pp.rewrite_paths: temp = re.sub(rewrite[0], rewrite[1], abssource) if temp != abssource: filename = temp if os.sep != "/": filename = filename.replace(os.sep, "/") break return _pcpp_filter(filename, fp) return _preprocess_file cxxheaderparser-1.3.1/cxxheaderparser/py.typed000066400000000000000000000000001455035044000215610ustar00rootroot00000000000000cxxheaderparser-1.3.1/cxxheaderparser/simple.py000066400000000000000000000247601455035044000217500ustar00rootroot00000000000000""" The simple parser/collector iterates over the C++ file and returns a data structure with all elements in it. Not quite as flexible as implementing your own parser listener, but you can accomplish most things with it. cxxheaderparser's unit tests predominantly use the simple API for parsing, so you can expect it to be pretty stable. The :func:`parse_string` and :func:`parse_file` functions are a great place to start: .. code-block:: python from cxxheaderparser.simple import parse_string content = ''' int x; ''' parsed_data = parse_string(content) See below for the contents of the returned :class:`ParsedData`. """ import os import sys import inspect import typing from dataclasses import dataclass, field from .types import ( ClassDecl, Concept, DeductionGuide, EnumDecl, Field, ForwardDecl, FriendDecl, Function, Method, NamespaceAlias, TemplateInst, Typedef, UsingAlias, UsingDecl, Variable, Value, ) from .parserstate import ( ClassBlockState, ExternBlockState, NamespaceBlockState, ) from .parser import CxxParser from .options import ParserOptions # # Data structure # @dataclass class ClassScope: """ Contains all data collected for a single C++ class """ #: Information about the class declaration is here class_decl: ClassDecl #: Nested classes classes: typing.List["ClassScope"] = field(default_factory=list) enums: typing.List[EnumDecl] = field(default_factory=list) fields: typing.List[Field] = field(default_factory=list) friends: typing.List[FriendDecl] = field(default_factory=list) methods: typing.List[Method] = field(default_factory=list) typedefs: typing.List[Typedef] = field(default_factory=list) forward_decls: typing.List[ForwardDecl] = field(default_factory=list) using: typing.List[UsingDecl] = field(default_factory=list) using_alias: typing.List[UsingAlias] = field(default_factory=list) @dataclass class NamespaceScope: """ Contains all data collected for a single namespace. Content for child namespaces are found in the ``namespaces`` attribute. """ name: str = "" inline: bool = False doxygen: typing.Optional[str] = None classes: typing.List["ClassScope"] = field(default_factory=list) enums: typing.List[EnumDecl] = field(default_factory=list) #: Function declarations (with or without body) functions: typing.List[Function] = field(default_factory=list) #: Method implementations outside of a class (must have a body) method_impls: typing.List[Method] = field(default_factory=list) typedefs: typing.List[Typedef] = field(default_factory=list) variables: typing.List[Variable] = field(default_factory=list) forward_decls: typing.List[ForwardDecl] = field(default_factory=list) using: typing.List[UsingDecl] = field(default_factory=list) using_ns: typing.List["UsingNamespace"] = field(default_factory=list) using_alias: typing.List[UsingAlias] = field(default_factory=list) ns_alias: typing.List[NamespaceAlias] = field(default_factory=list) #: Concepts concepts: typing.List[Concept] = field(default_factory=list) #: Explicit template instantiations template_insts: typing.List[TemplateInst] = field(default_factory=list) #: Child namespaces namespaces: typing.Dict[str, "NamespaceScope"] = field(default_factory=dict) #: Deduction guides deduction_guides: typing.List[DeductionGuide] = field(default_factory=list) Block = typing.Union[ClassScope, NamespaceScope] @dataclass class Pragma: content: Value @dataclass class Include: #: The filename includes the surrounding ``<>`` or ``"`` filename: str @dataclass class UsingNamespace: ns: str @dataclass class ParsedData: """ Container for information parsed by the :func:`parse_file` and :func:`parse_string` functions. .. warning:: Names are not resolved, so items are stored in the scope that they are found. For example: .. code-block:: c++ namespace N { class C; } class N::C { void fn(); }; The 'C' class would be a forward declaration in the 'N' namespace, but the ClassDecl for 'C' would be stored in the global namespace instead of the 'N' namespace. """ #: Global namespace namespace: NamespaceScope = field(default_factory=lambda: NamespaceScope()) #: Any ``#pragma`` directives encountered pragmas: typing.List[Pragma] = field(default_factory=list) #: Any ``#include`` directives encountered includes: typing.List[Include] = field(default_factory=list) # # Visitor implementation # # define what user data we store in each state type SClassBlockState = ClassBlockState[ClassScope, Block] SExternBlockState = ExternBlockState[NamespaceScope, NamespaceScope] SNamespaceBlockState = NamespaceBlockState[NamespaceScope, NamespaceScope] SState = typing.Union[SClassBlockState, SExternBlockState, SNamespaceBlockState] SNonClassBlockState = typing.Union[SExternBlockState, SNamespaceBlockState] class SimpleCxxVisitor: """ A simple visitor that stores all of the C++ elements passed to it in an "easy" to use data structure You probably don't want to use this directly, use :func:`parse_file` or :func:`parse_string` instead. """ data: ParsedData def on_parse_start(self, state: SNamespaceBlockState) -> None: ns = NamespaceScope("") self.data = ParsedData(ns) state.user_data = ns def on_pragma(self, state: SState, content: Value) -> None: self.data.pragmas.append(Pragma(content)) def on_include(self, state: SState, filename: str) -> None: self.data.includes.append(Include(filename)) def on_extern_block_start(self, state: SExternBlockState) -> typing.Optional[bool]: state.user_data = state.parent.user_data return None def on_extern_block_end(self, state: SExternBlockState) -> None: pass def on_namespace_start(self, state: SNamespaceBlockState) -> typing.Optional[bool]: parent_ns = state.parent.user_data ns = None names = state.namespace.names if not names: # all anonymous namespaces in a translation unit are the same names = [""] for name in names: ns = parent_ns.namespaces.get(name) if ns is None: ns = NamespaceScope(name) parent_ns.namespaces[name] = ns parent_ns = ns assert ns is not None # only set inline/doxygen on inner namespace ns.inline = state.namespace.inline ns.doxygen = state.namespace.doxygen state.user_data = ns return None def on_namespace_end(self, state: SNamespaceBlockState) -> None: pass def on_concept(self, state: SNonClassBlockState, concept: Concept) -> None: state.user_data.concepts.append(concept) def on_namespace_alias( self, state: SNonClassBlockState, alias: NamespaceAlias ) -> None: state.user_data.ns_alias.append(alias) def on_forward_decl(self, state: SState, fdecl: ForwardDecl) -> None: state.user_data.forward_decls.append(fdecl) def on_template_inst(self, state: SState, inst: TemplateInst) -> None: assert isinstance(state.user_data, NamespaceScope) state.user_data.template_insts.append(inst) def on_variable(self, state: SState, v: Variable) -> None: assert isinstance(state.user_data, NamespaceScope) state.user_data.variables.append(v) def on_function(self, state: SNonClassBlockState, fn: Function) -> None: state.user_data.functions.append(fn) def on_method_impl(self, state: SNonClassBlockState, method: Method) -> None: state.user_data.method_impls.append(method) def on_typedef(self, state: SState, typedef: Typedef) -> None: state.user_data.typedefs.append(typedef) def on_using_namespace( self, state: SNonClassBlockState, namespace: typing.List[str] ) -> None: ns = UsingNamespace("::".join(namespace)) state.user_data.using_ns.append(ns) def on_using_alias(self, state: SState, using: UsingAlias) -> None: state.user_data.using_alias.append(using) def on_using_declaration(self, state: SState, using: UsingDecl) -> None: state.user_data.using.append(using) # # Enums # def on_enum(self, state: SState, enum: EnumDecl) -> None: state.user_data.enums.append(enum) # # Class/union/struct # def on_class_start(self, state: SClassBlockState) -> typing.Optional[bool]: parent = state.parent.user_data block = ClassScope(state.class_decl) parent.classes.append(block) state.user_data = block return None def on_class_field(self, state: SClassBlockState, f: Field) -> None: state.user_data.fields.append(f) def on_class_method(self, state: SClassBlockState, method: Method) -> None: state.user_data.methods.append(method) def on_class_friend(self, state: SClassBlockState, friend: FriendDecl) -> None: state.user_data.friends.append(friend) def on_class_end(self, state: SClassBlockState) -> None: pass def on_deduction_guide( self, state: SNonClassBlockState, guide: DeductionGuide ) -> None: state.user_data.deduction_guides.append(guide) def parse_string( content: str, *, filename: str = "", options: typing.Optional[ParserOptions] = None, cleandoc: bool = False, ) -> ParsedData: """ Simple function to parse a header and return a data structure """ if cleandoc: content = inspect.cleandoc(content) visitor = SimpleCxxVisitor() parser = CxxParser(filename, content, visitor, options) parser.parse() return visitor.data def parse_file( filename: typing.Union[str, os.PathLike], encoding: typing.Optional[str] = None, *, options: typing.Optional[ParserOptions] = None, ) -> ParsedData: """ Simple function to parse a header from a file and return a data structure """ filename = os.fsdecode(filename) if encoding is None: encoding = "utf-8-sig" if filename == "-": content = sys.stdin.read() else: content = None visitor = SimpleCxxVisitor() parser = CxxParser(filename, content, visitor, options) parser.parse() return visitor.data cxxheaderparser-1.3.1/cxxheaderparser/tokfmt.py000066400000000000000000000052251455035044000217560ustar00rootroot00000000000000from dataclasses import dataclass, field import typing from .lexer import LexToken, PlyLexer, LexerTokenStream # key: token type, value: (left spacing, right spacing) _want_spacing = { "FLOAT_CONST": (2, 2), "HEX_FLOAT_CONST": (2, 2), "INT_CONST_HEX": (2, 2), "INT_CONST_BIN": (2, 2), "INT_CONST_OCT": (2, 2), "INT_CONST_DEC": (2, 2), "INT_CONST_CHAR": (2, 2), "NAME": (2, 2), "CHAR_CONST": (2, 2), "WCHAR_CONST": (2, 2), "U8CHAR_CONST": (2, 2), "U16CHAR_CONST": (2, 2), "U32CHAR_CONST": (2, 2), "STRING_LITERAL": (2, 2), "WSTRING_LITERAL": (2, 2), "U8STRING_LITERAL": (2, 2), "U16STRING_LITERAL": (2, 2), "U32STRING_LITERAL": (2, 2), "ELLIPSIS": (2, 2), ">": (0, 2), ")": (0, 1), "(": (1, 0), ",": (0, 3), "*": (1, 2), "&": (0, 2), } _want_spacing.update(dict.fromkeys(PlyLexer.keywords, (2, 2))) @dataclass class Token: """ In an ideal world, this Token class would not be exposed via the user visible API. Unfortunately, getting to that point would take a significant amount of effort. It is not expected that these will change, but they might. At the moment, the only supported use of Token objects are in conjunction with the ``tokfmt`` function. As this library matures, we'll try to clarify the expectations around these. File an issue on github if you have ideas! """ #: Raw value of the token value: str #: Lex type of the token type: str = field(repr=False, compare=False, default="") def tokfmt(toks: typing.List[Token]) -> str: """ Helper function that takes a list of tokens and converts them to a string """ last = 0 vals = [] default = (0, 0) ws = _want_spacing for tok in toks: value = tok.value # special case if value == "operator": l, r = 2, 0 else: l, r = ws.get(tok.type, default) if l + last >= 3: vals.append(" ") last = r vals.append(value) return "".join(vals) if __name__ == "__main__": # pragma: no cover import argparse parser = argparse.ArgumentParser() parser.add_argument("header") args = parser.parse_args() filename: str = args.header with open(filename) as fp: lexer = LexerTokenStream(filename, fp.read()) toks: typing.List[Token] = [] while True: tok = lexer.token_eof_ok() if not tok: break if tok.type == ";": print(toks) print(tokfmt(toks)) toks = [] else: toks.append(Token(tok.value, tok.type)) print(toks) print(tokfmt(toks)) cxxheaderparser-1.3.1/cxxheaderparser/types.py000066400000000000000000000516071455035044000216230ustar00rootroot00000000000000import typing from dataclasses import dataclass, field from .tokfmt import tokfmt, Token @dataclass class Value: """ A unparsed list of tokens .. code-block:: c++ int x = 0x1337; ~~~~~~ """ #: Tokens corresponding to the value tokens: typing.List[Token] def format(self) -> str: return tokfmt(self.tokens) @dataclass class NamespaceAlias: """ A namespace alias .. code-block:: c++ namespace ANS = my::ns; ~~~ ~~~~~~ """ alias: str #: These are the names (split by ::) for the namespace that this alias #: refers to, but does not include any parent namespace names. It may #: include a leading "::", but does not include a following :: string. names: typing.List[str] @dataclass class NamespaceDecl: """ Namespace declarations .. code-block:: c++ namespace foo::bar {} ~~~~~~~~ """ #: These are the names (split by ::) for this namespace declaration, #: but does not include any parent namespace names #: #: An anonymous namespace is an empty list names: typing.List[str] inline: bool = False #: Documentation if present doxygen: typing.Optional[str] = None @dataclass class DecltypeSpecifier: """ Contents of a decltype (inside the parentheses) .. code-block:: c++ decltype(Foo::Bar) ~~~~~~~~ """ #: Unparsed tokens within the decltype tokens: typing.List[Token] def format(self) -> str: return f"decltype({tokfmt(self.tokens)})" @dataclass class FundamentalSpecifier: """ A specifier that only contains fundamental types. Fundamental types include various combinations of the following: unsigned, signed, short, int, long, float, double, char, bool, char16_t, char32_t, nullptr_t, wchar_t, void """ name: str def format(self) -> str: return self.name @dataclass class NameSpecifier: """ An individual segment of a type name .. code-block:: c++ Foo::Bar ~~~ """ name: str specialization: typing.Optional["TemplateSpecialization"] = None def format(self) -> str: if self.specialization: return f"{self.name}{self.specialization.format()}" else: return self.name @dataclass class AutoSpecifier: """ Used for an auto return type """ name: str = "auto" def format(self) -> str: return self.name @dataclass class AnonymousName: """ A name for an anonymous class, such as in a typedef. There is no string associated with this name, only an integer id. Things that share the same anonymous name have anonymous name instances with the same id """ #: Unique id associated with this name (only unique per parser instance!) id: int def format(self) -> str: # TODO: not sure what makes sense here, subject to change return f"<>" PQNameSegment = typing.Union[ AnonymousName, FundamentalSpecifier, NameSpecifier, DecltypeSpecifier, AutoSpecifier ] @dataclass class PQName: """ Possibly qualified name of a C++ type. """ #: All of the segments of the name. This is always guaranteed to have at #: least one element in it. Name is segmented by '::' #: #: If a name refers to the global namespace, the first segment will be an #: empty NameSpecifier segments: typing.List[PQNameSegment] #: Set if the name starts with class/enum/struct classkey: typing.Optional[str] = None #: Set to true if the type was preceded with 'typename' has_typename: bool = False def format(self) -> str: tn = "typename " if self.has_typename else "" if self.classkey: return f"{tn}{self.classkey} {'::'.join(seg.format() for seg in self.segments)}" else: return tn + "::".join(seg.format() for seg in self.segments) @dataclass class TemplateArgument: """ A single argument for a template specialization .. code-block:: c++ Foo ~~~ """ #: If this argument is a type, it is stored here as a DecoratedType, #: otherwise it's stored as an unparsed set of values arg: typing.Union["DecoratedType", "FunctionType", Value] param_pack: bool = False def format(self) -> str: if self.param_pack: return f"{self.arg.format()}..." else: return self.arg.format() @dataclass class TemplateSpecialization: """ Contains the arguments of a template specialization .. code-block:: c++ Foo ~~~~~~~~~~~ """ args: typing.List[TemplateArgument] def format(self) -> str: return f"<{', '.join(arg.format() for arg in self.args)}>" @dataclass class FunctionType: """ A function type, currently only used in a function pointer .. note:: There can only be one of FunctionType or Type in a DecoratedType chain """ return_type: "DecoratedType" parameters: typing.List["Parameter"] #: If a member function pointer # TODO classname: typing.Optional[PQName] #: Set to True if ends with ``...`` vararg: bool = False #: True if function has a trailing return type (``auto foo() -> int``). #: In this case, the 'auto' return type is removed and replaced with #: whatever the trailing return type was has_trailing_return: bool = False noexcept: typing.Optional[Value] = None #: Only set if an MSVC calling convention (__stdcall, etc) is explictly #: specified. #: #: .. note:: If your code contains things like WINAPI, you will need to #: use a preprocessor to transform it to the appropriate #: calling convention msvc_convention: typing.Optional[str] = None def format(self) -> str: vararg = "..." if self.vararg else "" params = ", ".join(p.format() for p in self.parameters) if self.has_trailing_return: return f"auto ({params}{vararg}) -> {self.return_type.format()}" else: return f"{self.return_type.format()} ({params}{vararg})" def format_decl(self, name: str) -> str: """Format as a named declaration""" vararg = "..." if self.vararg else "" params = ", ".join(p.format() for p in self.parameters) if self.has_trailing_return: return f"auto {name}({params}{vararg}) -> {self.return_type.format()}" else: return f"{self.return_type.format()} {name}({params}{vararg})" @dataclass class Type: """ A type with a name associated with it """ typename: PQName const: bool = False volatile: bool = False def format(self) -> str: c = "const " if self.const else "" v = "volatile " if self.volatile else "" return f"{c}{v}{self.typename.format()}" def format_decl(self, name: str): """Format as a named declaration""" c = "const " if self.const else "" v = "volatile " if self.volatile else "" return f"{c}{v}{self.typename.format()} {name}" @dataclass class Array: """ Information about an array. Multidimensional arrays are represented as an array of array. """ #: The type that this is an array of array_of: typing.Union["Array", "Pointer", Type] #: Size of the array #: #: .. code-block:: c++ #: #: int x[10]; #: ~~ size: typing.Optional[Value] def format(self) -> str: s = self.size.format() if self.size else "" return f"{self.array_of.format()}[{s}]" def format_decl(self, name: str) -> str: s = self.size.format() if self.size else "" return f"{self.array_of.format()} {name}[{s}]" @dataclass class Pointer: """ A pointer """ #: Thing that this points to ptr_to: typing.Union[Array, FunctionType, "Pointer", Type] const: bool = False volatile: bool = False def format(self) -> str: c = " const" if self.const else "" v = " volatile" if self.volatile else "" ptr_to = self.ptr_to if isinstance(ptr_to, (Array, FunctionType)): return ptr_to.format_decl(f"(*{c}{v})") else: return f"{ptr_to.format()}*{c}{v}" def format_decl(self, name: str): """Format as a named declaration""" c = " const" if self.const else "" v = " volatile" if self.volatile else "" ptr_to = self.ptr_to if isinstance(ptr_to, (Array, FunctionType)): return ptr_to.format_decl(f"(*{c}{v} {name})") else: return f"{ptr_to.format()}*{c}{v} {name}" @dataclass class Reference: """ A lvalue (``&``) reference """ ref_to: typing.Union[Array, FunctionType, Pointer, Type] def format(self) -> str: ref_to = self.ref_to if isinstance(ref_to, Array): return ref_to.format_decl("(&)") else: return f"{ref_to.format()}&" def format_decl(self, name: str): """Format as a named declaration""" ref_to = self.ref_to if isinstance(ref_to, Array): return ref_to.format_decl(f"(& {name})") else: return f"{ref_to.format()}& {name}" @dataclass class MoveReference: """ An rvalue (``&&``) reference """ moveref_to: typing.Union[Array, FunctionType, Pointer, Type] def format(self) -> str: return f"{self.moveref_to.format()}&&" def format_decl(self, name: str): """Format as a named declaration""" return f"{self.moveref_to.format()}&& {name}" #: A type or function type that is decorated with various things #: #: .. note:: There can only be one of FunctionType or Type in a DecoratedType #: chain DecoratedType = typing.Union[Array, Pointer, MoveReference, Reference, Type] @dataclass class Enumerator: """ An individual value of an enumeration """ #: The enumerator key name name: str #: None if not explicitly specified value: typing.Optional[Value] = None #: Documentation if present doxygen: typing.Optional[str] = None @dataclass class EnumDecl: """ An enumeration type """ typename: PQName values: typing.List[Enumerator] base: typing.Optional[PQName] = None #: Documentation if present doxygen: typing.Optional[str] = None #: If within a class, the access level for this decl access: typing.Optional[str] = None @dataclass class TemplateNonTypeParam: """ .. code-block:: c++ template ~~~~~ template ~~~~~~~~~~~~~~~~~~~ template ~~~~~~ // abbreviated template parameters are converted to this and param_idx is set void fn(C auto p) ~~~~~~ """ type: DecoratedType name: typing.Optional[str] = None default: typing.Optional[Value] = None #: If this was promoted, the parameter index that this corresponds with param_idx: typing.Optional[int] = None #: Contains a ``...`` param_pack: bool = False @dataclass class TemplateTypeParam: """ .. code-block:: c++ template ~~~~~~~~~~ """ #: 'typename' or 'class' typekey: str name: typing.Optional[str] = None param_pack: bool = False default: typing.Optional[Value] = None #: A template-template param template: typing.Optional["TemplateDecl"] = None #: A parameter for a template declaration #: #: .. code-block:: c++ #: #: template #: ~~~~~~~~~~ TemplateParam = typing.Union[TemplateNonTypeParam, TemplateTypeParam] @dataclass class TemplateDecl: """ Template declaration for a function or class .. code-block:: c++ template class Foo {}; template T fn(); """ params: typing.List[TemplateParam] = field(default_factory=list) # Currently don't interpret requires, if that changes in the future # then this API will change. #: template requires ... raw_requires_pre: typing.Optional[Value] = None #: If no template, this is None. This is a TemplateDecl if this there is a single #: declaration: #: #: .. code-block:: c++ #: #: template #: struct C {}; #: #: If there are multiple template declarations, then this is a list of #: declarations in the order that they're encountered: #: #: .. code-block:: c++ #: #: template<> #: template #: struct A::C {}; #: TemplateDeclTypeVar = typing.Union[None, TemplateDecl, typing.List[TemplateDecl]] @dataclass class TemplateInst: """ Explicit template instantiation .. code-block:: c++ template class MyClass<1,2>; extern template class MyClass<2,3>; """ typename: PQName extern: bool doxygen: typing.Optional[str] = None @dataclass class Concept: """ Preliminary support for consuming headers that contain concepts, but not trying to actually make sense of them at this time. If this is something you care about, pull requests are welcomed! .. code-block:: c++ template concept Meowable = is_meowable; template concept Addable = requires (T x) { x + x; }; """ template: TemplateDecl name: str #: In the future this will be removed if we fully parse the expression raw_constraint: Value doxygen: typing.Optional[str] = None @dataclass class ForwardDecl: """ Represents a forward declaration of a user defined type """ typename: PQName template: TemplateDeclTypeVar = None doxygen: typing.Optional[str] = None #: Set if this is a forward declaration of an enum and it has a base enum_base: typing.Optional[PQName] = None #: If within a class, the access level for this decl access: typing.Optional[str] = None @dataclass class BaseClass: """ Base class declarations for a class """ #: access specifier for this base access: str #: possibly qualified type name for the base typename: PQName #: Virtual inheritance virtual: bool = False #: Contains a ``...`` param_pack: bool = False @dataclass class ClassDecl: """ A class is a user defined type (class/struct/union) """ typename: PQName bases: typing.List[BaseClass] = field(default_factory=list) template: TemplateDeclTypeVar = None explicit: bool = False final: bool = False doxygen: typing.Optional[str] = None #: If within a class, the access level for this decl access: typing.Optional[str] = None @property def classkey(self) -> typing.Optional[str]: return self.typename.classkey @dataclass class Parameter: """ A parameter of a function/method """ type: DecoratedType name: typing.Optional[str] = None default: typing.Optional[Value] = None param_pack: bool = False def format(self) -> str: default = f" = {self.default.format()}" if self.default else "" pp = "... " if self.param_pack else "" name = self.name if name: return f"{self.type.format_decl(f'{pp}{name}')}{default}" else: return f"{self.type.format()}{pp}{default}" @dataclass class Function: """ A function declaration, potentially with the function body """ #: Only constructors and destructors don't have a return type return_type: typing.Optional[DecoratedType] name: PQName parameters: typing.List[Parameter] #: Set to True if ends with ``...`` vararg: bool = False doxygen: typing.Optional[str] = None constexpr: bool = False extern: typing.Union[bool, str] = False static: bool = False inline: bool = False #: If true, the body of the function is present has_body: bool = False #: True if function has a trailing return type (``auto foo() -> int``). #: In this case, the 'auto' return type is removed and replaced with #: whatever the trailing return type was has_trailing_return: bool = False template: TemplateDeclTypeVar = None #: Value of any throw specification for this function. The value omits the #: outer parentheses. throw: typing.Optional[Value] = None #: Value of any noexcept specification for this function. The value omits #: the outer parentheses. noexcept: typing.Optional[Value] = None #: Only set if an MSVC calling convention (__stdcall, etc) is explictly #: specified. #: #: .. note:: If your code contains things like WINAPI, you will need to #: use a preprocessor to transform it to the appropriate #: calling convention msvc_convention: typing.Optional[str] = None #: The operator type (+, +=, etc). #: #: If this object is a Function, then this is a free operator function. If #: this object is a Method, then it is an operator method. #: #: In the case of a conversion operator (such as 'operator bool'), this #: is the string "conversion" and the full Type is found in return_type operator: typing.Optional[str] = None #: A requires constraint following the function declaration. If you need the #: prior, look at TemplateDecl.raw_requires_pre. At the moment this is just #: a raw value, if we interpret it in the future this will change. #: #: template int main() requires ... raw_requires: typing.Optional[Value] = None @dataclass class Method(Function): """ A method declaration, potentially with the method body """ #: If parsed within a class, the access level for this method access: typing.Optional[str] = None const: bool = False volatile: bool = False #: ref-qualifier for this method, either lvalue (&) or rvalue (&&) #: #: .. code-block:: c++ #: #: void foo() &&; #: ~~ #: ref_qualifier: typing.Optional[str] = None constructor: bool = False explicit: bool = False default: bool = False deleted: bool = False destructor: bool = False pure_virtual: bool = False virtual: bool = False final: bool = False override: bool = False @dataclass class FriendDecl: """ Represents a friend declaration -- friends can only be classes or functions """ cls: typing.Optional[ForwardDecl] = None fn: typing.Optional[Function] = None @dataclass class Typedef: """ A typedef specifier. A unique typedef specifier is created for each alias created by the typedef. .. code-block:: c++ typedef type name, *pname; """ #: The aliased type or function type #: #: .. code-block:: c++ #: #: typedef type *pname; #: ~~~~~~ type: typing.Union[DecoratedType, FunctionType] #: The alias introduced for the specified type #: #: .. code-block:: c++ #: #: typedef type *pname; #: ~~~~~ name: str #: If within a class, the access level for this decl access: typing.Optional[str] = None @dataclass class Variable: """ A variable declaration """ name: PQName type: DecoratedType value: typing.Optional[Value] = None constexpr: bool = False extern: typing.Union[bool, str] = False static: bool = False inline: bool = False #: Can occur for a static variable for a templated class template: typing.Optional[TemplateDecl] = None doxygen: typing.Optional[str] = None @dataclass class Field: """ A field of a class """ #: public/private/protected access: str type: DecoratedType name: typing.Optional[str] = None value: typing.Optional[Value] = None bits: typing.Optional[int] = None constexpr: bool = False mutable: bool = False static: bool = False inline: bool = False doxygen: typing.Optional[str] = None @dataclass class UsingDecl: """ .. code-block:: c++ using NS::ClassName; """ typename: PQName #: If within a class, the access level for this decl access: typing.Optional[str] = None #: Documentation if present doxygen: typing.Optional[str] = None @dataclass class UsingAlias: """ .. code-block:: c++ using foo = int; template using VectorT = std::vector; """ alias: str type: DecoratedType template: typing.Optional[TemplateDecl] = None #: If within a class, the access level for this decl access: typing.Optional[str] = None #: Documentation if present doxygen: typing.Optional[str] = None @dataclass class DeductionGuide: """ .. code-block:: c++ template MyClass(T) -> MyClass(int); """ #: Only constructors and destructors don't have a return type result_type: typing.Optional[DecoratedType] name: PQName parameters: typing.List[Parameter] doxygen: typing.Optional[str] = None cxxheaderparser-1.3.1/cxxheaderparser/visitor.py000066400000000000000000000212331455035044000221460ustar00rootroot00000000000000import sys import typing if sys.version_info >= (3, 8): from typing import Protocol else: Protocol = object # pragma: no cover from .types import ( Concept, DeductionGuide, EnumDecl, Field, ForwardDecl, FriendDecl, Function, Method, NamespaceAlias, TemplateInst, Typedef, UsingAlias, UsingDecl, Variable, Value, ) from .parserstate import ( State, ClassBlockState, ExternBlockState, NamespaceBlockState, NonClassBlockState, ) class CxxVisitor(Protocol): """ Defines the interface used by the parser to emit events """ def on_parse_start(self, state: NamespaceBlockState) -> None: """ Called when parsing begins """ def on_pragma(self, state: State, content: Value) -> None: """ Called once for each ``#pragma`` directive encountered """ def on_include(self, state: State, filename: str) -> None: """ Called once for each ``#include`` directive encountered """ def on_extern_block_start(self, state: ExternBlockState) -> typing.Optional[bool]: """ .. code-block:: c++ extern "C" { } If this function returns False, the visitor will not be called for any items inside this block (including on_extern_block_end) """ def on_extern_block_end(self, state: ExternBlockState) -> None: """ Called when an extern block ends """ def on_namespace_start(self, state: NamespaceBlockState) -> typing.Optional[bool]: """ Called when a ``namespace`` directive is encountered If this function returns False, the visitor will not be called for any items inside this namespace (including on_namespace_end) """ def on_namespace_end(self, state: NamespaceBlockState) -> None: """ Called at the end of a ``namespace`` block """ def on_namespace_alias( self, state: NonClassBlockState, alias: NamespaceAlias ) -> None: """ Called when a ``namespace`` alias is encountered """ def on_concept(self, state: NonClassBlockState, concept: Concept) -> None: """ .. code-block:: c++ template concept Meowable = is_meowable; """ def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: """ Called when a forward declaration is encountered """ def on_template_inst(self, state: State, inst: TemplateInst) -> None: """ Called when an explicit template instantiation is encountered """ def on_variable(self, state: State, v: Variable) -> None: """ Called when a global variable is encountered """ def on_function(self, state: NonClassBlockState, fn: Function) -> None: """ Called when a function is encountered that isn't part of a class """ def on_method_impl(self, state: NonClassBlockState, method: Method) -> None: """ Called when a method implementation is encountered outside of a class declaration. For example: .. code-block:: c++ void MyClass::fn() { // does something } .. note:: The above implementation is ambiguous, as it technically could be a function in a namespace. We emit this instead as it's more likely to be the case in common code. """ def on_typedef(self, state: State, typedef: Typedef) -> None: """ Called for each typedef instance encountered. For example: .. code-block:: c++ typedef int T, *PT; Will result in ``on_typedef`` being called twice, once for ``T`` and once for ``*PT`` """ def on_using_namespace( self, state: NonClassBlockState, namespace: typing.List[str] ) -> None: """ .. code-block:: c++ using namespace std; """ def on_using_alias(self, state: State, using: UsingAlias) -> None: """ .. code-block:: c++ using foo = int; template using VectorT = std::vector; """ def on_using_declaration(self, state: State, using: UsingDecl) -> None: """ .. code-block:: c++ using NS::ClassName; """ # # Enums # def on_enum(self, state: State, enum: EnumDecl) -> None: """ Called after an enum is encountered """ # # Class/union/struct # def on_class_start(self, state: ClassBlockState) -> typing.Optional[bool]: """ Called when a class/struct/union is encountered When part of a typedef: .. code-block:: c++ typedef struct { } X; This is called first, followed by on_typedef for each typedef instance encountered. The compound type object is passed as the type to the typedef. If this function returns False, the visitor will not be called for any items inside this class (including on_class_end) """ def on_class_field(self, state: ClassBlockState, f: Field) -> None: """ Called when a field of a class is encountered """ def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: """ Called when a friend declaration is encountered """ def on_class_method(self, state: ClassBlockState, method: Method) -> None: """ Called when a method of a class is encountered inside of a class """ def on_class_end(self, state: ClassBlockState) -> None: """ Called when the end of a class/struct/union is encountered. When a variable like this is declared: .. code-block:: c++ struct X { } x; Then ``on_class_start``, .. ``on_class_end`` are emitted, along with ``on_variable`` for each instance declared. """ def on_deduction_guide( self, state: NonClassBlockState, guide: DeductionGuide ) -> None: """ Called when a deduction guide is encountered """ class NullVisitor: """ This visitor does nothing """ def on_parse_start(self, state: NamespaceBlockState) -> None: return None def on_pragma(self, state: State, content: Value) -> None: return None def on_include(self, state: State, filename: str) -> None: return None def on_extern_block_start(self, state: ExternBlockState) -> typing.Optional[bool]: return None def on_extern_block_end(self, state: ExternBlockState) -> None: return None def on_namespace_start(self, state: NamespaceBlockState) -> typing.Optional[bool]: return None def on_namespace_end(self, state: NamespaceBlockState) -> None: return None def on_concept(self, state: NonClassBlockState, concept: Concept) -> None: return None def on_namespace_alias( self, state: NonClassBlockState, alias: NamespaceAlias ) -> None: return None def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None: return None def on_template_inst(self, state: State, inst: TemplateInst) -> None: return None def on_variable(self, state: State, v: Variable) -> None: return None def on_function(self, state: NonClassBlockState, fn: Function) -> None: return None def on_method_impl(self, state: NonClassBlockState, method: Method) -> None: return None def on_typedef(self, state: State, typedef: Typedef) -> None: return None def on_using_namespace( self, state: NonClassBlockState, namespace: typing.List[str] ) -> None: return None def on_using_alias(self, state: State, using: UsingAlias) -> None: return None def on_using_declaration(self, state: State, using: UsingDecl) -> None: return None def on_enum(self, state: State, enum: EnumDecl) -> None: return None def on_class_start(self, state: ClassBlockState) -> typing.Optional[bool]: return None def on_class_field(self, state: ClassBlockState, f: Field) -> None: return None def on_class_friend(self, state: ClassBlockState, friend: FriendDecl) -> None: return None def on_class_method(self, state: ClassBlockState, method: Method) -> None: return None def on_class_end(self, state: ClassBlockState) -> None: return None def on_deduction_guide( self, state: NonClassBlockState, guide: DeductionGuide ) -> None: return None null_visitor = NullVisitor() cxxheaderparser-1.3.1/docs/000077500000000000000000000000001455035044000156345ustar00rootroot00000000000000cxxheaderparser-1.3.1/docs/.gitignore000066400000000000000000000000071455035044000176210ustar00rootroot00000000000000/_buildcxxheaderparser-1.3.1/docs/Makefile000066400000000000000000000011721455035044000172750ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) cxxheaderparser-1.3.1/docs/conf.py000066400000000000000000000030331455035044000171320ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- import os import pkg_resources # -- Project information ----------------------------------------------------- project = "cxxheaderparser" copyright = "2020-2023, Dustin Spicuzza" author = "Dustin Spicuzza" # The full version, including alpha/beta/rc tags release = pkg_resources.get_distribution("cxxheaderparser").version # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx.ext.autodoc", "sphinx_autodoc_typehints", "sphinx_rtd_theme"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = "sphinx_rtd_theme" always_document_param_types = True cxxheaderparser-1.3.1/docs/custom.rst000066400000000000000000000020001455035044000176700ustar00rootroot00000000000000Custom parsing ============== For many users, the data provided by the simple API is enough. In some advanced cases you may find it necessary to use this more customizable parsing mechanism. First, define a visitor that implements the :class:`CxxVisitor` protocol. Then you can create an instance of it and pass it to the :class:`CxxParser`. .. code-block:: python visitor = MyVisitor() parser = CxxParser(filename, content, visitor) parser.parse() # do something with the data collected by the visitor Your visitor should do something with the data as the various callbacks are called. See the :class:`SimpleCxxVisitor` for inspiration. API --- .. automodule:: cxxheaderparser.parser :members: :undoc-members: .. automodule:: cxxheaderparser.visitor :members: :undoc-members: Parser state ------------ .. automodule:: cxxheaderparser.parserstate :members: :undoc-members: Preprocessor ------------ .. automodule:: cxxheaderparser.preprocessor :members: :undoc-members: cxxheaderparser-1.3.1/docs/index.rst000066400000000000000000000017301455035044000174760ustar00rootroot00000000000000.. cxxheaderparser documentation master file, created by sphinx-quickstart on Thu Dec 31 00:46:02 2020. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. cxxheaderparser =============== A pure python C++ header parser that parses C++ headers in a mildly naive manner that allows it to handle many C++ constructs, including many modern (C++11 and beyond) features. .. warning:: cxxheaderparser intentionally does not use a C preprocessor by default. If you are parsing code with macros in it, you need to provide a preprocessor function in :py:class:`.ParserOptions`. .. seealso:: :py:attr:`cxxheaderparser.options.ParserOptions.preprocessor` .. _pcpp: https://github.com/ned14/pcpp .. toctree:: :maxdepth: 2 :caption: Contents: tools simple custom types Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` cxxheaderparser-1.3.1/docs/make.bat000066400000000000000000000014331455035044000172420ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd cxxheaderparser-1.3.1/docs/requirements.txt000066400000000000000000000000741455035044000211210ustar00rootroot00000000000000sphinx >= 3.0 sphinx-rtd-theme sphinx-autodoc-typehints pcppcxxheaderparser-1.3.1/docs/simple.rst000066400000000000000000000002641455035044000176610ustar00rootroot00000000000000.. _simple: Simple API ========== .. automodule:: cxxheaderparser.simple :members: :undoc-members: .. automodule:: cxxheaderparser.options :members: :undoc-members:cxxheaderparser-1.3.1/docs/tools.rst000066400000000000000000000016111455035044000175250ustar00rootroot00000000000000Tools ===== There are a variety of command line tools provided by the cxxheaderparser project. dump tool --------- Dump data from a header to stdout .. code-block:: sh # pprint format python -m cxxheaderparser myheader.h # JSON format python -m cxxheaderparser --mode=json myheader.h # dataclasses repr format python -m cxxheaderparser --mode=repr myheader.h # dataclasses repr format (formatted with black) python -m cxxheaderparser --mode=brepr myheader.h Anything more than that and you should use the python API, start with the :ref:`simple API ` first. test generator -------------- To generate a unit test for cxxheaderparser: * Put the C++ header content in a file * Run the following: .. code-block:: sh python -m cxxheaderparser.gentest FILENAME.h TESTNAME You can copy/paste the stdout to one of the test files in the tests directory.cxxheaderparser-1.3.1/docs/types.rst000066400000000000000000000003141455035044000175300ustar00rootroot00000000000000Types ===== parser types ------------ .. automodule:: cxxheaderparser.types :members: :undoc-members: exceptions ---------- .. automodule:: cxxheaderparser.errors :members: :undoc-members:cxxheaderparser-1.3.1/mypy.ini000066400000000000000000000002001455035044000163730ustar00rootroot00000000000000[mypy] exclude = setup\.py|docs [mypy-pcpp.*] ignore_missing_imports = True [mypy-cxxheaderparser._ply.*] ignore_errors = Truecxxheaderparser-1.3.1/setup.py000066400000000000000000000050431455035044000164200ustar00rootroot00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function from os.path import dirname, exists, join import sys, subprocess from setuptools import find_packages, setup setup_dir = dirname(__file__) git_dir = join(setup_dir, ".git") version_file = join(setup_dir, "cxxheaderparser", "version.py") # Automatically generate a version.py based on the git version if exists(git_dir): p = subprocess.Popen( ["git", "describe", "--tags", "--long", "--dirty=-dirty"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) out, err = p.communicate() # Make sure the git version has at least one tag if err: print("Error: You need to create a tag for this repo to use the builder") sys.exit(1) # Convert git version to PEP440 compliant version # - Older versions of pip choke on local identifiers, so we can't include the git commit v, commits, local = out.decode("utf-8").rstrip().split("-", 2) if commits != "0" or "-dirty" in local: v = "%s.post0.dev%s" % (v, commits) # Create the version.py file with open(version_file, "w") as fp: fp.write("# Autogenerated by setup.py\n__version__ = '{0}'".format(v)) with open(version_file, "r") as fp: exec(fp.read(), globals()) DESCRIPTION = ( "Parse C++ header files and generate a data structure representing the class" ) CLASSIFIERS = [ "Development Status :: 4 - Beta", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: C++", "License :: OSI Approved :: BSD License", "Intended Audience :: Developers", "Topic :: Software Development", "Topic :: Software Development :: Code Generators", "Topic :: Software Development :: Compilers", ] setup( name="cxxheaderparser", version=__version__, author="Dustin Spicuzza", author_email="dustin@virtualroadside.com", maintainer="RobotPy Development Team", maintainer_email="robotpy@googlegroups.com", url="https://github.com/robotpy/cxxheaderparser", description=DESCRIPTION, long_description=open("README.md").read(), long_description_content_type="text/markdown", install_requires=["dataclasses; python_version < '3.7'"], extras_require={"pcpp": ["pcpp~=1.30"]}, license="BSD", platforms="Platform Independent", packages=find_packages(), package_data={"cxxheaderparser": ["py.typed"]}, keywords="c++ header parser ply", python_requires=">= 3.6", classifiers=CLASSIFIERS, ) cxxheaderparser-1.3.1/tests/000077500000000000000000000000001455035044000160465ustar00rootroot00000000000000cxxheaderparser-1.3.1/tests/README.md000066400000000000000000000013561455035044000173320ustar00rootroot00000000000000Tests ===== To run the tests, install `cxxheaderparser` and `pytest`, then just run: pytest Adding new tests ---------------- There's a helper script in cxxheaderparser explicitly for generating many of the unit tests in this directory. To run it: * Create a file with your C++ content in it * Run `python -m cxxheaderparser.gentest FILENAME.h some_name` * Copy the stdout to one of these `test_*.py` files Content origin -------------- * Some are scraps of real code derived from various sources * Some were derived from the original `CppHeaderParser` tests * Some have been derived from examples found on https://en.cppreference.com, which are available under Creative Commons Attribution-Sharealike 3.0 Unported License (CC-BY-SA) cxxheaderparser-1.3.1/tests/requirements.txt000066400000000000000000000000211455035044000213230ustar00rootroot00000000000000pytest pcpp~=1.30cxxheaderparser-1.3.1/tests/test_abv_template.py000066400000000000000000000314631455035044000221310ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` # # Tests various aspects of abbreviated function templates # from cxxheaderparser.simple import NamespaceScope, ParsedData, parse_string from cxxheaderparser.types import ( AutoSpecifier, Function, FundamentalSpecifier, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateDecl, TemplateNonTypeParam, Type, ) def test_abv_template_f1() -> None: content = """ void f1(auto); // same as template void f1(T) void f1p(auto p); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f1")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])) ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type(typename=PQName(segments=[AutoSpecifier()])), param_idx=0, ) ] ), ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f1p")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])), name="p", ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type(typename=PQName(segments=[AutoSpecifier()])), param_idx=0, ) ] ), ), ] ) ) def test_abv_template_f2() -> None: content = """ void f2(C1 auto); // same as template void f2(T), if C1 is a concept void f2p(C1 auto p); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f2")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])) ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C1")]) ), param_idx=0, ) ] ), ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f2p")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])), name="p", ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C1")]) ), param_idx=0, ) ] ), ), ] ) ) def test_abv_template_f3() -> None: content = """ void f3(C2 auto...); // same as template void f3(Ts...), if C2 is a // concept void f3p(C2 auto p...); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f3")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])), param_pack=True, ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C2")]) ), param_idx=0, param_pack=True, ) ] ), ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f3p")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])), name="p", param_pack=True, ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C2")]) ), param_idx=0, param_pack=True, ) ] ), ), ] ) ) def test_abv_template_f4() -> None: content = """ void f4(C2 auto, ...); // same as template void f4(T...), if C2 is a concept void f4p(C2 auto p,...); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f4")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])) ) ], vararg=True, template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C2")]) ), param_idx=0, ) ] ), ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f4p")]), parameters=[ Parameter( type=Type(typename=PQName(segments=[AutoSpecifier()])), name="p", ) ], vararg=True, template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C2")]) ), param_idx=0, ) ] ), ), ] ) ) def test_abv_template_f5() -> None: content = """ void f5(const C3 auto *, C4 auto &); // same as template void f5(const T*, U&); void f5p(const C3 auto * p1, C4 auto &p2); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f5")]), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[AutoSpecifier()], ), const=True, ) ) ), Parameter( type=Reference( ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) ) ), ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[NameSpecifier(name="C3")] ), ), param_idx=0, ), TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C4")]) ), param_idx=1, ), ] ), ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f5p")]), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[AutoSpecifier()], ), const=True, ) ), name="p1", ), Parameter( type=Reference( ref_to=Type(typename=PQName(segments=[AutoSpecifier()])) ), name="p2", ), ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[NameSpecifier(name="C3")] ), ), param_idx=0, ), TemplateNonTypeParam( type=Type( typename=PQName(segments=[NameSpecifier(name="C4")]) ), param_idx=1, ), ] ), ), ] ) ) cxxheaderparser-1.3.1/tests/test_attributes.py000066400000000000000000000156611455035044000216560ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( ClassDecl, EnumDecl, Enumerator, Field, FriendDecl, Function, FundamentalSpecifier, Method, NameSpecifier, PQName, Pointer, TemplateDecl, TemplateTypeParam, Token, Type, Typedef, Value, Variable, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_attributes_everywhere() -> None: # TODO: someday we'll actually support storing attributes, # but for now just make sure they don't get in the way content = """ struct [[deprecated]] S {}; [[deprecated]] typedef S *PS; [[deprecated]] int x; union U { [[deprecated]] int n; }; [[deprecated]] void f(); enum [[deprecated]] E{A [[deprecated]], B [[deprecated]] = 42}; struct alignas(8) AS {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ) ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="U")], classkey="union" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="n", ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="AS")], classkey="struct" ) ) ), ], enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), values=[ Enumerator(name="A"), Enumerator(name="B", value=Value(tokens=[Token(value="42")])), ], ) ], functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[], ) ], typedefs=[ Typedef( type=Pointer( ptr_to=Type(typename=PQName(segments=[NameSpecifier(name="S")])) ), name="PS", ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ) ], ) ) def test_attributes_gcc_enum_packed() -> None: content = """ enum Wheat { w1, w2, w3, } __attribute__((packed)); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="Wheat")], classkey="enum" ), values=[ Enumerator(name="w1"), Enumerator(name="w2"), Enumerator(name="w3"), ], ) ] ) ) def test_friendly_declspec() -> None: content = """ struct D { friend __declspec(dllexport) void my_friend(); static __declspec(dllexport) void static_declspec(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="D")], classkey="struct" ) ), friends=[ FriendDecl( fn=Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="my_friend")]), parameters=[], access="public", ) ) ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[NameSpecifier(name="static_declspec")] ), parameters=[], static=True, access="public", ) ], ) ] ) ) def test_declspec_template() -> None: content = """ template __declspec(deprecated("message")) static T2 fn() { return T2(); } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T2")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], static=True, has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T2")] ), ) ] ) ) cxxheaderparser-1.3.1/tests/test_class.py000066400000000000000000003717501455035044000206010ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( AnonymousName, Array, BaseClass, ClassDecl, EnumDecl, Enumerator, Field, ForwardDecl, Function, FundamentalSpecifier, Method, MoveReference, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateSpecialization, TemplateTypeParam, Token, Type, Typedef, UsingDecl, Value, Variable, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_class_member_spec_1() -> None: content = """ class S { int d1; // non-static data member int a[10] = {1, 2}; // non-static data member with initializer (C++11) static const int d2 = 1; // static data member with initializer virtual void f1(int) = 0; // pure virtual member function std::string d3, *d4, f2(int); // two data members and a member function enum { NORTH, SOUTH, EAST, WEST }; struct NestedS { std::string s; } d5, *d6; typedef NestedS value_type, *pointer_type; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="class" ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="NestedS")], classkey="struct", ), access="private", ), fields=[ Field( name="s", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="string"), ] ) ), access="public", ) ], ) ], enums=[ EnumDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ), values=[ Enumerator(name="NORTH"), Enumerator(name="SOUTH"), Enumerator(name="EAST"), Enumerator(name="WEST"), ], access="private", ) ], fields=[ Field( name="d1", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="private", ), Field( name="a", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="10")]), ), access="private", value=Value( tokens=[ Token(value="{"), Token(value="1"), Token(value=","), Token(value="2"), Token(value="}"), ] ), ), Field( name="d2", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ), const=True, ), access="private", value=Value(tokens=[Token(value="1")]), static=True, ), Field( name="d3", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="string"), ] ) ), access="private", ), Field( name="d4", type=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="string"), ] ) ) ), access="private", ), Field( name="d5", type=Type( typename=PQName( segments=[NameSpecifier(name="NestedS")], classkey="struct", ) ), access="private", ), Field( name="d6", type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="NestedS")], classkey="struct", ) ) ), access="private", ), ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="f1")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], access="private", pure_virtual=True, virtual=True, ), Method( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="string"), ] ) ), name=PQName(segments=[NameSpecifier(name="f2")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], access="private", ), ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="NestedS")] ) ), name="value_type", access="private", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="NestedS")] ) ) ), name="pointer_type", access="private", ), ], ) ] ) ) def test_class_member_spec_2() -> None: content = """ class M { std::size_t C; std::vector data; public: M(std::size_t R, std::size_t C) : C(C), data(R * C) {} // constructor definition int operator()(size_t r, size_t c) const { // member function definition return data[r * C + c]; } int &operator()(size_t r, size_t c) { // another member function definition return data[r * C + c]; } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="M")], classkey="class" ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="C", ), Field( access="private", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="vector", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ] ), ), ] ) ), name="data", ), ], methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="M")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="R", ), Parameter( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="C", ), ], has_body=True, access="public", constructor=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name=PQName(segments=[NameSpecifier(name="operator()")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="size_t")] ) ), name="r", ), Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="size_t")] ) ), name="c", ), ], has_body=True, access="public", const=True, operator="()", ), Method( return_type=Reference( ref_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), name=PQName(segments=[NameSpecifier(name="operator()")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="size_t")] ) ), name="r", ), Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="size_t")] ) ), name="c", ), ], has_body=True, access="public", operator="()", ), ], ) ] ) ) def test_class_member_spec_3() -> None: content = """ class S { public: S(); // public constructor S(const S &); // public copy constructor virtual ~S(); // public virtual destructor private: int *ptr; // private data member }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="class" ) ), fields=[ Field( name="ptr", type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), access="private", ) ], methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="S")]), parameters=[], access="public", constructor=True, ), Method( return_type=None, name=PQName(segments=[NameSpecifier(name="S")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="S")] ), const=True, ) ) ) ], access="public", constructor=True, ), Method( return_type=None, name=PQName(segments=[NameSpecifier(name="~S")]), parameters=[], access="public", destructor=True, virtual=True, ), ], ) ] ) ) def test_class_using() -> None: content = """ class Base { protected: int d; }; class Derived : public Base { public: using Base::Base; // inherit all parent's constructors (C++11) using Base::d; // make Base's protected member d a public member of Derived }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Base")], classkey="class" ) ), fields=[ Field( name="d", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="protected", ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Derived")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Base")]), ) ], ), using=[ UsingDecl( typename=PQName( segments=[ NameSpecifier(name="Base"), NameSpecifier(name="Base"), ] ), access="public", ), UsingDecl( typename=PQName( segments=[ NameSpecifier(name="Base"), NameSpecifier(name="d"), ] ), access="public", ), ], ), ] ) ) def test_class_member_spec_6() -> None: content = """ struct S { template void f(T&& n); template struct NestedS { std::basic_string s; }; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="NestedS")], classkey="struct", ), template=TemplateDecl( params=[ TemplateTypeParam(typekey="class", name="CharT") ] ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="basic_string", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="CharT" ) ] ) ) ) ] ), ), ] ) ), name="s", ) ], ) ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[ Parameter( type=MoveReference( moveref_to=Type( typename=PQName( segments=[NameSpecifier(name="T")] ) ) ), name="n", ) ], template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), access="public", ) ], ) ] ) ) def test_class_fn_default_params() -> None: content = """ // clang-format off class Hen { public: void add(int a=100, b=0xfd, float c=1.7e-3, float d=3.14); void join(string s1="", string s2="nothing"); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Hen")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="a", default=Value(tokens=[Token(value="100")]), ), Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="b")] ) ), default=Value(tokens=[Token(value="0xfd")]), ), Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="float") ] ) ), name="c", default=Value(tokens=[Token(value="1.7e-3")]), ), Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="float") ] ) ), name="d", default=Value(tokens=[Token(value="3.14")]), ), ], access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="join")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="string")] ) ), name="s1", default=Value(tokens=[Token(value='""')]), ), Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="string")] ) ), name="s2", default=Value(tokens=[Token(value='"nothing"')]), ), ], access="public", ), ], ) ] ) ) def test_class_fn_inline_virtual() -> None: content = """ class B { public: virtual inline int aMethod(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="B")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name=PQName(segments=[NameSpecifier(name="aMethod")]), parameters=[], inline=True, access="public", virtual=True, ) ], ) ] ) ) def test_class_fn_pure_virtual_const() -> None: content = """ class StoneClass { virtual int getNum2() const = 0; int getNum3(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="StoneClass")], classkey="class", ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name=PQName(segments=[NameSpecifier(name="getNum2")]), parameters=[], access="private", const=True, pure_virtual=True, virtual=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name=PQName(segments=[NameSpecifier(name="getNum3")]), parameters=[], access="private", ), ], ) ] ) ) def test_class_fn_return_global_ns() -> None: content = """ struct Avacado { uint8_t foo() { return 4; } ::uint8_t bar() { return 0; } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Avacado")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[NameSpecifier(name="uint8_t")] ) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], has_body=True, access="public", ), Method( return_type=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="uint8_t"), ] ) ), name=PQName(segments=[NameSpecifier(name="bar")]), parameters=[], has_body=True, access="public", ), ], ) ] ) ) def test_class_ns_class() -> None: content = """ namespace ns { class N; }; class ns::N {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier(name="ns"), NameSpecifier(name="N"), ], classkey="class", ) ) ) ], namespaces={ "ns": NamespaceScope( name="ns", forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="N")], classkey="class" ) ) ], ) }, ) ) def test_class_ns_w_base() -> None: content = """ class Herb::Cilantro : public Plant {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier(name="Herb"), NameSpecifier(name="Cilantro"), ], classkey="class", ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Plant")]), ) ], ) ) ] ) ) def test_class_inner_class() -> None: content = """ class C { class Inner {}; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Inner")], classkey="class", ), access="private", ) ) ], ) ] ) ) def test_class_inner_fwd_class() -> None: content = """ class C { class N; }; class C::N {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ) ), forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="N")], classkey="class" ), access="private", ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C"), NameSpecifier(name="N")], classkey="class", ) ) ), ] ) ) def test_class_inner_var_access() -> None: content = """ class Bug_3488053 { public: class Bug_3488053_Nested { public: int x; }; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Bug_3488053")], classkey="class", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Bug_3488053_Nested")], classkey="class", ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", ) ], ) ], ) ] ) ) def test_class_ns_and_inner() -> None: content = """ namespace RoosterNamespace { class RoosterOuterClass { public: int member1; class RoosterSubClass1 { public: int publicMember1; private: int privateMember1; }; private: int member2; class RoosterSubClass2 { public: int publicMember2; private: int privateMember2; }; }; } // namespace RoosterNamespace """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( namespaces={ "RoosterNamespace": NamespaceScope( name="RoosterNamespace", classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="RoosterOuterClass")], classkey="class", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier(name="RoosterSubClass1") ], classkey="class", ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), name="publicMember1", ), Field( access="private", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), name="privateMember1", ), ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier(name="RoosterSubClass2") ], classkey="class", ), access="private", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), name="publicMember2", ), Field( access="private", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), name="privateMember2", ), ], ), ], fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="member1", ), Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="member2", ), ], ) ], ) } ) ) def test_class_struct_access() -> None: content = """ struct SampleStruct { unsigned int meth(); private: int prop; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="SampleStruct")], classkey="struct", ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="prop", ) ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="unsigned int")] ) ), name=PQName(segments=[NameSpecifier(name="meth")]), parameters=[], access="public", ) ], ) ] ) ) def test_class_volatile_move_deleted_fn() -> None: content = """ struct C { void foo() volatile && = delete; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], access="public", volatile=True, ref_qualifier="&&", deleted=True, ) ], ) ] ) ) def test_class_bitfield_1() -> None: content = """ struct S { // will usually occupy 2 bytes: // 3 bits: value of b1 // 2 bits: unused // 6 bits: value of b2 // 2 bits: value of b3 // 3 bits: unused unsigned char b1 : 3, : 2, b2 : 6, b3 : 2; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ) ), fields=[ Field( name="b1", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="unsigned char") ] ) ), access="public", bits=3, ), Field( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="unsigned char") ] ) ), access="public", bits=2, ), Field( name="b2", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="unsigned char") ] ) ), access="public", bits=6, ), Field( name="b3", type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="unsigned char") ] ) ), access="public", bits=2, ), ], ) ] ) ) def test_class_bitfield_2() -> None: content = """ struct HAL_ControlWord { int x : 1; int y : 1; }; typedef struct HAL_ControlWord HAL_ControlWord; int HAL_GetControlWord(HAL_ControlWord *controlWord); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="HAL_ControlWord")], classkey="struct", ) ), fields=[ Field( name="x", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", bits=1, ), Field( name="y", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", bits=1, ), ], ) ], functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="HAL_GetControlWord")]), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="HAL_ControlWord")] ) ) ), name="controlWord", ) ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="HAL_ControlWord")], classkey="struct", ) ), name="HAL_ControlWord", ) ], ) ) def test_class_anon_struct_as_globalvar() -> None: content = """ struct { int m; } unnamed, *p_unnamed; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( classkey="struct", segments=[AnonymousName(id=1)] ) ), fields=[ Field( name="m", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")], ) ), access="public", ) ], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="unnamed")]), type=Type( typename=PQName( classkey="struct", segments=[AnonymousName(id=1)] ) ), ), Variable( name=PQName(segments=[NameSpecifier(name="p_unnamed")]), type=Pointer( ptr_to=Type( typename=PQName( classkey="struct", segments=[AnonymousName(id=1)] ) ) ), ), ], ) ) def test_class_anon_struct_as_classvar() -> None: content = """ struct AnonHolderClass { struct { int x; } a; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="AnonHolderClass")], classkey="struct", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", ) ], ) ], fields=[ Field( access="public", type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="a", ) ], ) ] ) ) def test_class_anon_struct_as_unnamed_classvar() -> None: content = """ struct AnonHolderClass { struct { int x; int y; }; int z; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="AnonHolderClass")], classkey="struct", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="y", ), ], ) ], fields=[ Field( access="public", type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="z", ), ], ) ] ) ) def test_initializer_with_initializer_list_1() -> None: content = """ struct ComplexInit : SomeBase { ComplexInit(int i) : m_stuff{i, 2} { auto i = something(); } void fn(); std::vector m_stuff; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="ComplexInit")], classkey="struct", ), bases=[ BaseClass( access="public", typename=PQName( segments=[NameSpecifier(name="SomeBase")] ), ) ], ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="vector", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ] ), ), ] ) ), name="m_stuff", ) ], methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="ComplexInit")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="i", ) ], has_body=True, access="public", constructor=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], access="public", ), ], ) ] ) ) def test_initializer_with_initializer_list_2() -> None: content = """ template class future final { public: template future(future &&oth) noexcept : future(oth.then([](R &&val) -> T { return val; })) {} }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="future")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), final=True, ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="future")]), parameters=[ Parameter( type=MoveReference( moveref_to=Type( typename=PQName( segments=[ NameSpecifier( name="future", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="R" ) ] ) ) ) ] ), ) ] ) ) ), name="oth", ) ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="R")] ), noexcept=Value(tokens=[]), access="public", constructor=True, ) ], ) ] ) ) def test_class_with_arrays() -> None: content = """ const int MAX_ITEM = 7; class Bird { int items[MAX_ITEM]; int otherItems[7]; int oneItem; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Bird")], classkey="class" ) ), fields=[ Field( access="private", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="MAX_ITEM")]), ), name="items", ), Field( access="private", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="7")]), ), name="otherItems", ), Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="oneItem", ), ], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="MAX_ITEM")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]), const=True, ), value=Value(tokens=[Token(value="7")]), ) ], ) ) def test_class_fn_inline_impl() -> None: content = """ class Monkey { private: static void Create(); }; inline void Monkey::Create() {} """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Monkey")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="Create")]), parameters=[], static=True, access="private", ) ], ) ], method_impls=[ Method( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName( segments=[ NameSpecifier(name="Monkey"), NameSpecifier(name="Create"), ] ), parameters=[], inline=True, has_body=True, ) ], ) ) def test_class_fn_virtual_final_override() -> None: content = """ struct Lemon { virtual void foo() final; virtual void foo2(); }; struct Lime final : Lemon { void abc(); void foo2() override; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Lemon")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], access="public", virtual=True, final=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="foo2")]), parameters=[], access="public", virtual=True, ), ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Lime")], classkey="struct" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Lemon")]), ) ], final=True, ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="abc")]), parameters=[], access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="foo2")]), parameters=[], access="public", override=True, ), ], ), ] ) ) def test_class_fn_return_class() -> None: content = """ class Peach { int abc; }; class Plumb { class Peach *doSomethingGreat(class Peach *pInCurPtr); class Peach *var; }; class Peach *Plumb::myMethod(class Peach *pInPtr) { return pInPtr; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class" ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="abc", ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Plumb")], classkey="class" ) ), fields=[ Field( access="private", type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class", ) ) ), name="var", ) ], methods=[ Method( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class", ) ) ), name=PQName( segments=[NameSpecifier(name="doSomethingGreat")] ), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class", ) ) ), name="pInCurPtr", ) ], access="private", ) ], ), ], method_impls=[ Method( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class" ) ) ), name=PQName( segments=[ NameSpecifier(name="Plumb"), NameSpecifier(name="myMethod"), ] ), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="Peach")], classkey="class", ) ) ), name="pInPtr", ) ], has_body=True, ) ], ) ) def test_class_fn_template_impl() -> None: content = """ class Owl { private: template int *tFunc(int count); }; template int *Owl::tFunc(int count) { if (count == 0) { return NULL; } return NULL; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Owl")], classkey="class" ) ), methods=[ Method( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), name=PQName(segments=[NameSpecifier(name="tFunc")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="count", ) ], template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), access="private", ) ], ) ], method_impls=[ Method( return_type=Pointer( ptr_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ), name=PQName( segments=[ NameSpecifier(name="Owl"), NameSpecifier(name="tFunc"), ] ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="count", ) ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ], ) ) def test_class_fn_inline_template_impl() -> None: content = """ class Chicken { template static T Get(); }; template T Chicken::Get() { return T(); } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Chicken")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName(segments=[NameSpecifier(name="Get")]), parameters=[], static=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), access="private", ) ], ) ], method_impls=[ Method( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName( segments=[ NameSpecifier(name="Chicken"), NameSpecifier(name="Get"), ] ), parameters=[], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ], ) ) def test_class_fn_explicit_constructors() -> None: content = """ class Lizzard { Lizzard(); explicit Lizzard(int a); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Lizzard")], classkey="class" ) ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="Lizzard")]), parameters=[], access="private", constructor=True, ), Method( return_type=None, name=PQName(segments=[NameSpecifier(name="Lizzard")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="a", ) ], access="private", constructor=True, explicit=True, ), ], ) ] ) ) def test_class_fn_default_constructor() -> None: content = """ class DefaultConstDest { public: DefaultConstDest() = default; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="DefaultConstDest")], classkey="class", ) ), methods=[ Method( return_type=None, name=PQName( segments=[NameSpecifier(name="DefaultConstDest")] ), parameters=[], access="public", constructor=True, default=True, ) ], ) ] ) ) def test_class_fn_delete_constructor() -> None: content = """ class A { public: A() = delete; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class" ) ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="A")]), parameters=[], access="public", constructor=True, deleted=True, ) ], ) ] ) ) def test_class_multi_vars() -> None: content = """ class Grape { public: int a, b, c; map d; map e, f; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Grape")], classkey="class" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="a", ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="b", ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="c", ), Field( access="public", type=Type( typename=PQName( segments=[ NameSpecifier( name="map", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="string" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), ] ), ) ] ) ), name="d", ), Field( access="public", type=Type( typename=PQName( segments=[ NameSpecifier( name="map", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="string" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), ] ), ) ] ) ), name="e", ), Field( access="public", type=Type( typename=PQName( segments=[ NameSpecifier( name="map", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="string" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), ] ), ) ] ) ), name="f", ), ], ) ] ) ) def test_class_static_const_var_expr() -> None: content = """ class PandaClass { static const int CONST_A = (1 << 7) - 1; static const int CONST_B = sizeof(int); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="PandaClass")], classkey="class", ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ), const=True, ), name="CONST_A", value=Value( tokens=[ Token(value="("), Token(value="1"), Token(value="<<"), Token(value="7"), Token(value=")"), Token(value="-"), Token(value="1"), ] ), static=True, ), Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ), const=True, ), name="CONST_B", value=Value( tokens=[ Token(value="sizeof"), Token(value="("), Token(value="int"), Token(value=")"), ] ), static=True, ), ], ) ] ) ) def test_class_fwd_struct() -> None: content = """ class PotatoClass { struct FwdStruct; FwdStruct *ptr; struct FwdStruct { int a; }; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="PotatoClass")], classkey="class", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="FwdStruct")], classkey="struct", ), access="private", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="a", ) ], ) ], fields=[ Field( access="private", type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="FwdStruct")] ) ) ), name="ptr", ) ], forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="FwdStruct")], classkey="struct", ), access="private", ) ], ) ] ) ) def test_class_multi_array() -> None: content = """ struct Picture { char name[25]; unsigned int pdata[128][256]; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Picture")], classkey="struct" ) ), fields=[ Field( access="public", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), size=Value(tokens=[Token(value="25")]), ), name="name", ), Field( access="public", type=Array( array_of=Array( array_of=Type( typename=PQName( segments=[ FundamentalSpecifier( name="unsigned int" ) ] ) ), size=Value(tokens=[Token(value="256")]), ), size=Value(tokens=[Token(value="128")]), ), name="pdata", ), ], ) ] ) ) def test_class_noexcept() -> None: content = """ struct Grackle { void no_noexcept(); void just_noexcept() noexcept; void const_noexcept() const noexcept; void noexcept_bool() noexcept(true); void const_noexcept_bool() const noexcept(true); void noexcept_noexceptOperator() noexcept(noexcept(Grackle())); void const_noexcept_noexceptOperator() const noexcept(noexcept(Grackle())); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Grackle")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="no_noexcept")]), parameters=[], access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="just_noexcept")]), parameters=[], noexcept=Value(tokens=[]), access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[NameSpecifier(name="const_noexcept")] ), parameters=[], noexcept=Value(tokens=[]), access="public", const=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="noexcept_bool")]), parameters=[], noexcept=Value(tokens=[Token(value="true")]), access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[NameSpecifier(name="const_noexcept_bool")] ), parameters=[], noexcept=Value(tokens=[Token(value="true")]), access="public", const=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier(name="noexcept_noexceptOperator") ] ), parameters=[], noexcept=Value( tokens=[ Token(value="noexcept"), Token(value="("), Token(value="Grackle"), Token(value="("), Token(value=")"), Token(value=")"), ] ), access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier( name="const_noexcept_noexceptOperator" ) ] ), parameters=[], noexcept=Value( tokens=[ Token(value="noexcept"), Token(value="("), Token(value="Grackle"), Token(value="("), Token(value=")"), Token(value=")"), ] ), access="public", const=True, ), ], ) ] ) ) def test_class_volatile() -> None: content = """ class Foo { public: private: volatile bool myToShutDown; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Foo")], classkey="class" ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="bool")] ), volatile=True, ), name="myToShutDown", ) ], ) ] ) ) def test_class_mutable() -> None: content = """ class Foo { private: mutable volatile Standard_Integer myRefCount_; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Foo")], classkey="class" ) ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[NameSpecifier(name="Standard_Integer")] ), volatile=True, ), name="myRefCount_", mutable=True, ) ], ) ] ) ) def test_nested_class_access() -> None: content = """ class Outer { struct Inner { void fn(); }; void ofn(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Outer")], classkey="class" ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Inner")], classkey="struct", ), access="private", ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], access="public", ) ], ) ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="ofn")]), parameters=[], access="private", ) ], ) ] ) ) def test_class_with_typedef() -> None: content = """ template class A { public: typedef B C; A(); protected: C aCInstance; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="SomeType")] ), ), fields=[ Field( access="protected", type=Type( typename=PQName(segments=[NameSpecifier(name="C")]) ), name="aCInstance", ) ], methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="A")]), parameters=[], access="public", constructor=True, ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[ NameSpecifier( name="B", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="SomeType" ) ] ) ) ) ] ), ) ] ) ), name="C", access="public", ) ], ) ] ) ) def test_class_ref_qualifiers() -> None: content = """ struct X { void fn0(); void fn1() &; void fn2() &&; void fn3() && = 0; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn0")]), parameters=[], access="public", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], access="public", ref_qualifier="&", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn2")]), parameters=[], access="public", ref_qualifier="&&", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[], access="public", ref_qualifier="&&", pure_virtual=True, ), ], ) ] ) ) def test_method_outside_class() -> None: content = """ int foo::bar() { return 1; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( method_impls=[ Method( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName( segments=[NameSpecifier(name="foo"), NameSpecifier(name="bar")] ), parameters=[], has_body=True, ) ] ) ) def test_constructor_outside_class() -> None: content = """ inline foo::foo() {} """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( method_impls=[ Method( return_type=None, name=PQName( segments=[NameSpecifier(name="foo"), NameSpecifier(name="foo")] ), parameters=[], inline=True, has_body=True, constructor=True, ) ] ) ) def test_class_inline_static() -> None: content = """ struct X { inline static bool Foo = 1; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="bool")] ) ), name="Foo", value=Value(tokens=[Token(value="1")]), static=True, inline=True, ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_class_base.py000066400000000000000000000212061455035044000215570ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( BaseClass, ClassDecl, Field, FundamentalSpecifier, Method, NameSpecifier, PQName, TemplateArgument, TemplateSpecialization, Token, Type, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_class_private_base() -> None: content = """ namespace Citrus { class BloodOrange { }; } class Bananna: public Citrus::BloodOrange { }; class ExcellentCake: private Citrus::BloodOrange, Convoluted::Nested::Mixin { }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Bananna")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier(name="Citrus"), NameSpecifier(name="BloodOrange"), ] ), ) ], ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="ExcellentCake")], classkey="class", ), bases=[ BaseClass( access="private", typename=PQName( segments=[ NameSpecifier(name="Citrus"), NameSpecifier(name="BloodOrange"), ] ), ), BaseClass( access="private", typename=PQName( segments=[ NameSpecifier(name="Convoluted"), NameSpecifier(name="Nested"), NameSpecifier(name="Mixin"), ] ), ), ], ) ), ], namespaces={ "Citrus": NamespaceScope( name="Citrus", classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="BloodOrange")], classkey="class", ) ) ) ], ) }, ) ) def test_class_virtual_base() -> None: content = """ class BaseMangoClass {}; class MangoClass : virtual public BaseMangoClass {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="BaseMangoClass")], classkey="class", ) ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="MangoClass")], classkey="class", ), bases=[ BaseClass( access="public", typename=PQName( segments=[NameSpecifier(name="BaseMangoClass")] ), virtual=True, ) ], ) ), ] ) ) def test_class_multiple_base_with_virtual() -> None: content = """ class BlueJay : public Bird, public virtual Food { public: BlueJay() {} }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="BlueJay")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Bird")]), ), BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Food")]), virtual=True, ), ], ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="BlueJay")]), parameters=[], has_body=True, access="public", constructor=True, ) ], ) ] ) ) def test_class_base_specialized() -> None: content = """ class Pea : public Vegetable { int i; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Pea")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="Vegetable", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Green" ) ] ) ) ) ] ), ) ] ), ) ], ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="i", ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_concepts.py000066400000000000000000001007001455035044000212730ustar00rootroot00000000000000from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string from cxxheaderparser.tokfmt import Token from cxxheaderparser.types import ( AutoSpecifier, ClassDecl, Concept, Function, FundamentalSpecifier, Method, MoveReference, NameSpecifier, PQName, Parameter, TemplateArgument, TemplateDecl, TemplateNonTypeParam, TemplateSpecialization, TemplateTypeParam, Type, Value, Variable, ) def test_concept_basic_constraint() -> None: content = """ template concept Derived = std::is_base_of::value; template T> void f(T); // T is constrained by Derived """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ) ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[ NameSpecifier( name="Derived", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Base" ) ] ) ) ) ] ), ) ] ) ), name="T", ) ] ), ) ], concepts=[ Concept( template=TemplateDecl( params=[ TemplateTypeParam(typekey="class", name="T"), TemplateTypeParam(typekey="class", name="U"), ] ), name="Derived", raw_constraint=Value( tokens=[ Token(value="std"), Token(value="::"), Token(value="is_base_of"), Token(value="<"), Token(value="U"), Token(value=","), Token(value="T"), Token(value=">"), Token(value="::"), Token(value="value"), ] ), ) ], ) ) def test_concept_basic_constraint2() -> None: content = """ template constexpr bool is_meowable = true; template constexpr bool is_cat = true; template concept Meowable = is_meowable; template concept BadMeowableCat = is_meowable && is_cat; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="is_meowable")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="bool")]) ), value=Value(tokens=[Token(value="true")]), constexpr=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), ), Variable( name=PQName(segments=[NameSpecifier(name="is_cat")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="bool")]) ), value=Value(tokens=[Token(value="true")]), constexpr=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), ), ], concepts=[ Concept( template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), name="Meowable", raw_constraint=Value( tokens=[ Token(value="is_meowable"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), Concept( template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), name="BadMeowableCat", raw_constraint=Value( tokens=[ Token(value="is_meowable"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="&&"), Token(value="is_cat"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), ], ) ) def test_concept_basic_requires() -> None: content = """ template concept Hashable = requires(T a) { { std::hash{}(a) } -> std::convertible_to; }; template void f(T) {} """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ) ) ], has_body=True, template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[NameSpecifier(name="Hashable")] ) ), name="T", ) ] ), ) ], concepts=[ Concept( template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), name="Hashable", raw_constraint=Value( tokens=[ Token(value="requires"), Token(value="("), Token(value="T"), Token(value="a"), Token(value=")"), Token(value="{"), Token(value="{"), Token(value="std"), Token(value="::"), Token(value="hash"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="{"), Token(value="}"), Token(value="("), Token(value="a"), Token(value=")"), Token(value="}"), Token(value="->"), Token(value="std"), Token(value="::"), Token(value="convertible_to"), Token(value="<"), Token(value="std"), Token(value="::"), Token(value="size_t"), Token(value=">"), Token(value=";"), Token(value="}"), ] ), ) ], ) ) def test_concept_nested_requirements() -> None: content = """ template concept Semiregular = DefaultConstructible && CopyConstructible && CopyAssignable && Destructible && requires(T a, std::size_t n) { requires Same; // nested: "Same<...> evaluates to true" { a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw requires Same; // nested: "Same<...> evaluates to true" requires Same; // nested { delete new T }; // compound { delete new T[n] }; // compound }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( concepts=[ Concept( template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), name="Semiregular", raw_constraint=Value( tokens=[ Token(value="DefaultConstructible"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="&&"), Token(value="CopyConstructible"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="&&"), Token(value="CopyAssignable"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="&&"), Token(value="Destructible"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="&&"), Token(value="requires"), Token(value="("), Token(value="T"), Token(value="a"), Token(value=","), Token(value="std"), Token(value="::"), Token(value="size_t"), Token(value="n"), Token(value=")"), Token(value="{"), Token(value="requires"), Token(value="Same"), Token(value="<"), Token(value="T"), Token(value="*"), Token(value=","), Token(value="decltype"), Token(value="("), Token(value="&"), Token(value="a"), Token(value=")"), Token(value=">"), Token(value=";"), Token(value="{"), Token(value="a"), Token(value="."), Token(value="~T"), Token(value="("), Token(value=")"), Token(value="}"), Token(value="noexcept"), Token(value=";"), Token(value="requires"), Token(value="Same"), Token(value="<"), Token(value="T"), Token(value="*"), Token(value=","), Token(value="decltype"), Token(value="("), Token(value="new"), Token(value="T"), Token(value=")"), Token(value=">"), Token(value=";"), Token(value="requires"), Token(value="Same"), Token(value="<"), Token(value="T"), Token(value="*"), Token(value=","), Token(value="decltype"), Token(value="("), Token(value="new"), Token(value="T"), Token(value="["), Token(value="n"), Token(value="]"), Token(value=")"), Token(value=">"), Token(value=";"), Token(value="{"), Token(value="delete"), Token(value="new"), Token(value="T"), Token(value="}"), Token(value=";"), Token(value="{"), Token(value="delete"), Token(value="new"), Token(value="T"), Token(value="["), Token(value="n"), Token(value="]"), Token(value="}"), Token(value=";"), Token(value="}"), ] ), ) ] ) ) def test_concept_requires_class() -> None: content = """ // clang-format off template concept Number = std::integral || std::floating_point; template requires Number struct WrappedNumber {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="WrappedNumber")], classkey="struct", ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="Number"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), ) ) ], concepts=[ Concept( template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), name="Number", raw_constraint=Value( tokens=[ Token(value="std"), Token(value="::"), Token(value="integral"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="||"), Token(value="std"), Token(value="::"), Token(value="floating_point"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ) ], ) ) def test_requires_last_elem() -> None: content = """ template void f(T&&) requires Eq; // can appear as the last element of a function declarator """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[ Parameter( type=MoveReference( moveref_to=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ) ) ) ], template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), raw_requires=Value( tokens=[ Token(value="Eq"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ) ] ) ) def test_requires_first_elem1() -> None: content = """ template requires Addable // or right after a template parameter list T add(T a, T b) { return a + b; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="a", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="b", ), ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="Addable"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), ) ] ) ) def test_requires_first_elem2() -> None: content = """ template requires std::is_arithmetic_v T add(T a, T b) { return a + b; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="a", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="b", ), ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="std"), Token(value="is_arithmetic_v"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), ) ] ) ) def test_requires_compound() -> None: content = """ template requires Addable || Subtractable T add(T a, T b) { return a + b; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="a", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="b", ), ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="Addable"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="||"), Token(value="Subtractable"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), ) ] ) ) def test_requires_ad_hoc() -> None: content = """ template requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice T add(T a, T b) { return a + b; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="a", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="b", ), ], has_body=True, template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="requires"), Token(value="("), Token(value="T"), Token(value="x"), Token(value=")"), Token(value="{"), Token(value="x"), Token(value="+"), Token(value="x"), Token(value=";"), Token(value="}"), ] ), ), ) ] ) ) def test_requires_both() -> None: content = """ // clang-format off template requires Addable auto f1(T a, T b) requires Subtractable; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type(typename=PQName(segments=[AutoSpecifier()])), name=PQName(segments=[NameSpecifier(name="f1")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="a", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="b", ), ], template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")], raw_requires_pre=Value( tokens=[ Token(value="Addable"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ), raw_requires=Value( tokens=[ Token(value="Subtractable"), Token(value="<"), Token(value="T"), Token(value=">"), ] ), ) ] ) ) def test_requires_paren() -> None: content = """ // clang-format off template void h(T) requires (is_purrable()); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="h")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ) ) ], template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), raw_requires=Value( tokens=[ Token(value="("), Token(value="is_purrable"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value="("), Token(value=")"), Token(value=")"), ] ), ) ] ) ) def test_non_template_requires() -> None: content = """ // clang-format off template struct Payload { constexpr Payload(T v) requires(std::is_pod_v) : Value(v) { } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Payload")], classkey="struct" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="T")] ), ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="Payload")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="T")] ) ), name="v", ) ], constexpr=True, has_body=True, raw_requires=Value( tokens=[ Token(value="("), Token(value="std"), Token(value="::"), Token(value="is_pod_v"), Token(value="<"), Token(value="T"), Token(value=">"), Token(value=")"), ] ), access="public", constructor=True, ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_doxygen.py000066400000000000000000000321041455035044000211340ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( AnonymousName, Array, BaseClass, ClassDecl, EnumDecl, Enumerator, Field, ForwardDecl, Function, FundamentalSpecifier, Method, MoveReference, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateSpecialization, TemplateTypeParam, Token, Type, Typedef, UsingDecl, UsingAlias, Value, Variable, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_doxygen_class() -> None: content = """ // clang-format off /// cls comment class C { /// member comment void fn(); /// var above int var_above; int var_after; /// var after }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ), doxygen="/// cls comment", ), fields=[ Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="var_above", doxygen="/// var above", ), Field( access="private", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="var_after", doxygen="/// var after", ), ], methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], doxygen="/// member comment", access="private", ) ], ) ] ) ) def test_doxygen_class_template() -> None: content = """ // clang-format off /// template comment template class C2 {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C2")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), doxygen="/// template comment", ) ) ] ) ) def test_doxygen_enum() -> None: content = """ // clang-format off /// /// @brief Rino Numbers, not that that means anything /// typedef enum { RI_ZERO, /// item zero RI_ONE, /** item one */ RI_TWO, //!< item two RI_THREE, /// item four RI_FOUR, } Rino; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[ Enumerator(name="RI_ZERO", doxygen="/// item zero"), Enumerator(name="RI_ONE", doxygen="/** item one */"), Enumerator(name="RI_TWO", doxygen="//!< item two"), Enumerator(name="RI_THREE"), Enumerator(name="RI_FOUR", doxygen="/// item four"), ], doxygen="///\n/// @brief Rino Numbers, not that that means anything\n///", ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="Rino", ) ], ) ) def test_doxygen_fn_3slash() -> None: content = """ // clang-format off /// fn comment void fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], doxygen="/// fn comment", ) ] ) ) def test_doxygen_fn_cstyle1() -> None: content = """ /** * fn comment */ void fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], doxygen="/**\n* fn comment\n*/", ) ] ) ) def test_doxygen_fn_cstyle2() -> None: content = """ /*! * fn comment */ void fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], doxygen="/*!\n* fn comment\n*/", ) ] ) ) def test_doxygen_var_above() -> None: content = """ // clang-format off /// var comment int v1 = 0; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="v1")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="0")]), doxygen="/// var comment", ) ] ) ) def test_doxygen_var_after() -> None: content = """ // clang-format off int v2 = 0; /// var2 comment """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="v2")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="0")]), doxygen="/// var2 comment", ) ] ) ) def test_doxygen_multiple_variables() -> None: content = """ int x; /// this is x int y; /// this is y /// this is also y int z; /// this is z """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), doxygen="/// this is x", ), Variable( name=PQName(segments=[NameSpecifier(name="y")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), doxygen="/// this is y\n/// this is also y", ), Variable( name=PQName(segments=[NameSpecifier(name="z")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), doxygen="/// this is z", ), ] ) ) def test_doxygen_namespace() -> None: content = """ /** * x is a mysterious namespace */ namespace x {} /** * c is also a mysterious namespace */ namespace a::b::c {} """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( namespaces={ "x": NamespaceScope( name="x", doxygen="/**\n* x is a mysterious namespace\n*/" ), "a": NamespaceScope( name="a", namespaces={ "b": NamespaceScope( name="b", namespaces={ "c": NamespaceScope( name="c", doxygen="/**\n* c is also a mysterious namespace\n*/", ) }, ) }, ), } ) ) def test_doxygen_declspec() -> None: content = """ /// declspec comment __declspec(thread) int i = 1; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="i")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="1")]), doxygen="/// declspec comment", ) ] ) ) def test_doxygen_attribute() -> None: content = """ /// hasattr comment [[nodiscard]] int hasattr(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="hasattr")]), parameters=[], doxygen="/// hasattr comment", ) ] ) ) def test_doxygen_using_decl() -> None: content = """ // clang-format off /// Comment using ns::ClassName; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using=[ UsingDecl( typename=PQName( segments=[ NameSpecifier(name="ns"), NameSpecifier(name="ClassName"), ] ), doxygen="/// Comment", ) ] ) ) def test_doxygen_using_alias() -> None: content = """ // clang-format off /// Comment using alias = sometype; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName(segments=[NameSpecifier(name="sometype")]) ), doxygen="/// Comment", ) ] ) ) cxxheaderparser-1.3.1/tests/test_enum.py000066400000000000000000000472131455035044000204320ustar00rootroot00000000000000from cxxheaderparser.types import ( AnonymousName, ClassDecl, EnumDecl, Enumerator, Field, ForwardDecl, Function, FundamentalSpecifier, NameSpecifier, PQName, Parameter, Pointer, Token, Type, Typedef, Value, Variable, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_basic_enum() -> None: content = """ enum Foo { A, B, }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="Foo")], classkey="enum" ), values=[Enumerator(name="A"), Enumerator(name="B")], ) ] ) ) def test_enum_w_expr() -> None: content = """ enum Foo { A = (1 / 2), B = 3, }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="Foo")], classkey="enum" ), values=[ Enumerator( name="A", value=Value( tokens=[ Token(value="("), Token(value="1"), Token(value="/"), Token(value="2"), Token(value=")"), ] ), ), Enumerator(name="B", value=Value(tokens=[Token(value="3")])), ], ) ] ) ) def test_enum_w_multiline_expr() -> None: content = r""" // clang-format off enum Author { NAME = ('J' << 24 | \ 'A' << 16 | \ 'S' << 8 | \ 'H'), }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="Author")], classkey="enum" ), values=[ Enumerator( name="NAME", value=Value( tokens=[ Token(value="("), Token(value="'J'"), Token(value="<<"), Token(value="24"), Token(value="|"), Token(value="'A'"), Token(value="<<"), Token(value="16"), Token(value="|"), Token(value="'S'"), Token(value="<<"), Token(value="8"), Token(value="|"), Token(value="'H'"), Token(value=")"), ] ), ) ], ) ] ) ) def test_basic_enum_class() -> None: content = """ enum class BE { BEX }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ), values=[Enumerator(name="BEX")], ) ] ) ) def test_basic_enum_struct() -> None: content = """ enum struct BE { BEX }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum struct" ), values=[Enumerator(name="BEX")], ) ] ) ) def test_enum_base() -> None: content = """ enum class E : int {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum class" ), values=[], base=PQName(segments=[FundamentalSpecifier(name="int")]), ) ] ) ) # instances def test_enum_instance_1() -> None: content = """ enum class BE { BEX } be1; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ), values=[Enumerator(name="BEX")], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="be1")]), type=Type( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ) ), ) ], ) ) def test_enum_instance_2() -> None: content = """ enum class BE { BEX } be1, *be2; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ), values=[Enumerator(name="BEX")], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="be1")]), type=Type( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ) ), ), Variable( name=PQName(segments=[NameSpecifier(name="be2")]), type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class", ) ) ), ), ], ) ) # bases in namespaces def test_enum_base_in_ns() -> None: content = """ namespace EN { typedef int EINT; }; enum class BE : EN::EINT { BEX }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum class" ), values=[Enumerator(name="BEX")], base=PQName( segments=[NameSpecifier(name="EN"), NameSpecifier(name="EINT")] ), ) ], namespaces={ "EN": NamespaceScope( name="EN", typedefs=[ Typedef( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="EINT", ) ], ) }, ) ) # forward declarations def test_enum_fwd() -> None: content = """ enum class BE1; enum class BE2 : EN::EINT; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="BE1")], classkey="enum class" ) ), ForwardDecl( typename=PQName( segments=[NameSpecifier(name="BE2")], classkey="enum class" ), enum_base=PQName( segments=[NameSpecifier(name="EN"), NameSpecifier(name="EINT")] ), ), ] ) ) def test_enum_private_in_class() -> None: content = """ class C { enum E { E1 }; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ) ), enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), values=[Enumerator(name="E1")], access="private", ) ], ) ] ) ) def test_enum_public_in_class() -> None: content = """ class C { public: enum E { E1 }; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ) ), enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), values=[Enumerator(name="E1")], access="public", ) ], ) ] ) ) def test_default_enum() -> None: content = """ class A { enum { v1, v2, } m_v1 = v1; enum { vv1, vv2, vv3 } m_v2 = vv2, m_v3 = vv3; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class" ) ), enums=[ EnumDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ), values=[Enumerator(name="v1"), Enumerator(name="v2")], access="private", ), EnumDecl( typename=PQName( segments=[AnonymousName(id=2)], classkey="enum" ), values=[ Enumerator(name="vv1"), Enumerator(name="vv2"), Enumerator(name="vv3"), ], access="private", ), ], fields=[ Field( access="private", type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ) ), name="m_v1", value=Value(tokens=[Token(value="v1")]), ), Field( access="private", type=Type( typename=PQName( segments=[AnonymousName(id=2)], classkey="enum" ) ), name="m_v2", value=Value(tokens=[Token(value="vv2")]), ), Field( access="private", type=Type( typename=PQName( segments=[AnonymousName(id=2)], classkey="enum" ) ), name="m_v3", value=Value(tokens=[Token(value="vv3")]), ), ], ) ] ) ) def test_enum_template_vals() -> None: content = """ enum { IsRandomAccess = std::is_base_of::value, IsBidirectional = std::is_base_of::value, }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[ Enumerator( name="IsRandomAccess", value=Value( tokens=[ Token(value="std"), Token(value="::"), Token(value="is_base_of"), Token(value="<"), Token(value="std"), Token(value="::"), Token(value="random_access_iterator_tag"), Token(value=","), Token(value="IteratorCategoryT"), Token(value=">"), Token(value="::"), Token(value="value"), ] ), ), Enumerator( name="IsBidirectional", value=Value( tokens=[ Token(value="std"), Token(value="::"), Token(value="is_base_of"), Token(value="<"), Token(value="std"), Token(value="::"), Token(value="bidirectional_iterator_tag"), Token(value=","), Token(value="IteratorCategoryT"), Token(value=">"), Token(value="::"), Token(value="value"), ] ), ), ], ) ] ) ) def test_enum_fn() -> None: content = """ enum E { VALUE, }; void fn_with_enum_param1(const enum E e); void fn_with_enum_param2(const enum E e) { // code here } enum E fn_with_enum_retval1(void); enum E fn_with_enum_retval2(void) { // code here } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), values=[Enumerator(name="VALUE")], ) ], functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn_with_enum_param1")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), const=True, ), name="e", ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn_with_enum_param2")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), const=True, ), name="e", ) ], has_body=True, ), Function( return_type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ) ), name=PQName(segments=[NameSpecifier(name="fn_with_enum_retval1")]), parameters=[], ), Function( return_type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ) ), name=PQName(segments=[NameSpecifier(name="fn_with_enum_retval2")]), parameters=[], has_body=True, ), ], ) ) cxxheaderparser-1.3.1/tests/test_fn.py000066400000000000000000001311241455035044000200640ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( Array, AutoSpecifier, ClassDecl, DecltypeSpecifier, Field, Function, FunctionType, FundamentalSpecifier, Method, MoveReference, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateSpecialization, TemplateTypeParam, Token, Type, Typedef, Value, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_fn_returns_class() -> None: content = """ class X *fn1(); struct Y fn2(); enum E fn3(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="X")], classkey="class" ) ) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], ), Function( return_type=Type( typename=PQName( segments=[NameSpecifier(name="Y")], classkey="struct" ) ), name=PQName(segments=[NameSpecifier(name="fn2")]), parameters=[], ), Function( return_type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[], ), ] ) ) def test_fn_returns_typename() -> None: content = """ typename ns::X fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="ns"), NameSpecifier(name="X"), ], has_typename=True, ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], ) ] ) ) def test_fn_returns_typename_const() -> None: content = """ const typename ns::X fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="ns"), NameSpecifier(name="X"), ], has_typename=True, ), const=True, ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], ) ] ) ) def test_fn_pointer_params() -> None: content = """ int fn1(int *); int fn2(int *p); int fn3(int(*p)); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn2")]), parameters=[ Parameter( name="p", type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[ Parameter( name="p", type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), ) ], ), ] ) ) def test_fn_void_is_no_params() -> None: content = """ int fn(void); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], ) ] ) ) def test_fn_array_param() -> None: content = """ void fn(int array[]); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[ Parameter( name="array", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=None, ), ) ], ) ] ) ) def test_fn_typename_param() -> None: content = """ void MethodA(const mynamespace::SomeObject &x, typename mynamespace::SomeObject * = 0); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="MethodA")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="mynamespace"), NameSpecifier(name="SomeObject"), ] ), const=True, ) ), name="x", ), Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier(name="mynamespace"), NameSpecifier(name="SomeObject"), ], has_typename=True, ) ) ), default=Value(tokens=[Token(value="0")]), ), ], ) ] ) ) def test_fn_weird_refs() -> None: content = """ int aref(int(&x)); void ptr_ref(int(*&name)); void ref_to_array(int (&array)[]); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="aref")]), parameters=[ Parameter( name="x", type=Reference( ref_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="ptr_ref")]), parameters=[ Parameter( name="name", type=Reference( ref_to=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ), ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="ref_to_array")]), parameters=[ Parameter( name="array", type=Reference( ref_to=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=None, ) ), ) ], ), ] ) ) def test_fn_too_many_parens() -> None: content = """ int fn1(int (x)); void (fn2 (int (*const (name)))); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[ Parameter( name="x", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), ) ], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn2")]), parameters=[ Parameter( name="name", type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), const=True, ), ) ], ), ] ) ) # TODO calling conventions """ void __stdcall fn(); void (__stdcall * fn) """ def test_fn_same_line() -> None: # multiple functions on the same line content = """ void fn1(), fn2(); void *fn3(), fn4(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn2")]), parameters=[], ), Function( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn4")]), parameters=[], ), ] ) ) def test_fn_auto_template() -> None: content = """ template auto add(T t, U u) { return t + u; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type(typename=PQName(segments=[AutoSpecifier()])), name=PQName(segments=[NameSpecifier(name="add")]), parameters=[ Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="T")]) ), name="t", ), Parameter( type=Type( typename=PQName(segments=[NameSpecifier(name="U")]) ), name="u", ), ], has_body=True, template=TemplateDecl( params=[ TemplateTypeParam(typekey="class", name="T"), TemplateTypeParam(typekey="class", name="U"), ] ), ) ] ) ) def test_fn_template_ptr() -> None: content = """ std::vector *fn(std::vector *ps); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="vector", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier( name="Pointer" ) ] ) ) ) ) ] ), ), ] ) ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="vector", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier( name="Pointer" ) ] ) ) ) ) ] ), ), ] ) ) ), name="ps", ) ], ) ] ) ) def test_fn_with_impl() -> None: content = """ // clang-format off void termite(void) { return ((structA*) (Func())->element); } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="termite")]), parameters=[], has_body=True, ) ] ) ) def test_fn_return_std_function() -> None: content = """ std::function fn(); """ data1 = parse_string(content, cleandoc=True) content = """ std::function fn(); """ data2 = parse_string(content, cleandoc=True) expected = ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="void" ) ] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ], ) ) ] ), ), ] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], ) ] ) ) assert data1 == expected assert data2 == expected def test_fn_return_std_function_trailing() -> None: content = """ std::functionint> fn(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ], has_trailing_return=True, ) ) ] ), ), ] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], ) ] ) ) def test_fn_trailing_return_simple() -> None: content = """ auto fn() -> int; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], has_trailing_return=True, ) ] ) ) def test_fn_trailing_return_std_function() -> None: content = """ auto fn() -> std::function; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ), parameters=[], ) ) ] ), ), ] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], has_trailing_return=True, ) ] ) ) def test_inline_volatile_fn() -> None: content = """ inline int Standard_Atomic_Increment (volatile int* theValue); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName( segments=[NameSpecifier(name="Standard_Atomic_Increment")] ), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ), volatile=True, ) ), name="theValue", ) ], inline=True, ) ] ) ) def test_method_w_reference() -> None: content = """ struct StreamBuffer { StreamBuffer &operator<<(std::ostream &(*fn)(std::ostream &)) { return *this; } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="StreamBuffer")], classkey="struct", ) ), methods=[ Method( return_type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="StreamBuffer")] ) ) ), name=PQName(segments=[NameSpecifier(name="operator<<")]), parameters=[ Parameter( type=Pointer( ptr_to=FunctionType( return_type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="ostream" ), ] ) ) ), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier( name="std" ), NameSpecifier( name="ostream" ), ] ) ) ) ) ], ) ), name="fn", ) ], has_body=True, access="public", operator="<<", ) ], ) ] ) ) def test_fn_w_mvreference() -> None: content = """ void fn1(int && (*)(int)); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[ Parameter( type=Pointer( ptr_to=FunctionType( return_type=MoveReference( moveref_to=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ) ) ], ) ) ) ], ) ] ) ) def test_msvc_conventions() -> None: content = """ void __cdecl fn(); typedef const char* (__stdcall *wglGetExtensionsStringARB_t)(HDC theDeviceContext); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], msvc_convention="__cdecl", ) ], typedefs=[ Typedef( type=Pointer( ptr_to=FunctionType( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ), const=True, ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="HDC")] ) ), name="theDeviceContext", ) ], msvc_convention="__stdcall", ) ), name="wglGetExtensionsStringARB_t", ) ], ) ) def test_throw_empty() -> None: content = """ void foo() throw() { throw std::runtime_error("foo"); } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], has_body=True, throw=Value(tokens=[]), ) ] ) ) def test_throw_dynamic() -> None: content = """ void foo() throw(std::exception) { throw std::runtime_error("foo"); } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], has_body=True, throw=Value( tokens=[ Token(value="std"), Token(value="::"), Token(value="exception"), ] ), ) ] ) ) def test_noexcept_empty() -> None: content = """ void foo() noexcept; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], noexcept=Value(tokens=[]), ) ] ) ) def test_noexcept_contents() -> None: content = """ void foo() noexcept(false); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[], noexcept=Value(tokens=[Token(value="false")]), ) ] ) ) def test_auto_decltype_return() -> None: content = """ class C { public: int x; auto GetSelected() -> decltype(x); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", ) ], methods=[ Method( return_type=Type( typename=PQName( segments=[ DecltypeSpecifier(tokens=[Token(value="x")]) ] ) ), name=PQName(segments=[NameSpecifier(name="GetSelected")]), parameters=[], has_trailing_return=True, access="public", ) ], ) ] ) ) def test_fn_trailing_return_with_body() -> None: content = """ auto test() -> void { } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="test")]), parameters=[], has_body=True, has_trailing_return=True, ) ] ) ) def test_method_trailing_return_with_body() -> None: content = """ struct X { auto test() -> void { } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="test")]), parameters=[], has_body=True, has_trailing_return=True, access="public", ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_friends.py000066400000000000000000000327561455035044000211260ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( ClassDecl, Field, ForwardDecl, FriendDecl, FundamentalSpecifier, Method, NameSpecifier, PQName, Parameter, Reference, TemplateDecl, TemplateTypeParam, Type, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) # friends def test_various_friends() -> None: content = """ class FX { public: FX(char); ~FX(); void fn() const; }; class FF { friend class FX; friend FX::FX(char), FX::~FX(); friend void FX::fn() const; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="FX")], classkey="class" ) ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="FX")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ) ) ], access="public", constructor=True, ), Method( return_type=None, name=PQName(segments=[NameSpecifier(name="~FX")]), parameters=[], access="public", destructor=True, ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], access="public", const=True, ), ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="FF")], classkey="class" ) ), friends=[ FriendDecl( cls=ForwardDecl( typename=PQName( segments=[NameSpecifier(name="FX")], classkey="class", ), access="private", ) ), FriendDecl( fn=Method( return_type=None, name=PQName( segments=[ NameSpecifier(name="FX"), NameSpecifier(name="FX"), ] ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="char") ] ) ) ) ], access="private", constructor=True, ) ), FriendDecl( fn=Method( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="FX"), NameSpecifier(name="FX"), ] ) ), name=PQName( segments=[ NameSpecifier(name="FX"), NameSpecifier(name="~FX"), ] ), parameters=[], access="private", ) ), FriendDecl( fn=Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier(name="FX"), NameSpecifier(name="fn"), ] ), parameters=[], access="private", const=True, ) ), ], ), ] ) ) def test_more_friends() -> None: content = """ template struct X { static int x; }; struct BFF { void fn() const; }; struct F { friend enum B; friend void BFF::fn() const; template friend class X; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ), fields=[ Field( name="x", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", static=True, ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="BFF")], classkey="struct" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[], access="public", const=True, ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="F")], classkey="struct" ) ), friends=[ FriendDecl( cls=ForwardDecl( typename=PQName( segments=[NameSpecifier(name="B")], classkey="enum" ), access="public", ) ), FriendDecl( fn=Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier(name="BFF"), NameSpecifier(name="fn"), ] ), parameters=[], access="public", const=True, ) ), FriendDecl( cls=ForwardDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="class" ), template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename", name="T") ] ), access="public", ) ), ], ), ] ) ) def test_friend_type_no_class() -> None: content = """ class DogClass; class CatClass { friend DogClass; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="CatClass")], classkey="class" ) ), friends=[ FriendDecl( cls=ForwardDecl( typename=PQName( segments=[NameSpecifier(name="DogClass")] ), access="private", ) ) ], ) ], forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="DogClass")], classkey="class" ) ) ], ) ) def test_friend_with_impl() -> None: content = """ // clang-format off class Garlic { public: friend int genNum(C& a) { return obj.meth().num(); } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Garlic")], classkey="class" ) ), friends=[ FriendDecl( fn=Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name=PQName(segments=[NameSpecifier(name="genNum")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="C")] ) ) ), name="a", ) ], has_body=True, access="public", ) ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_misc.py000066400000000000000000000211271455035044000204150ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( BaseClass, ClassDecl, Function, FundamentalSpecifier, NameSpecifier, PQName, Parameter, Token, Type, Value, Variable, ) from cxxheaderparser.simple import ( ClassScope, Include, NamespaceScope, Pragma, parse_string, ParsedData, ) import pytest # # minimal preprocessor support # def test_includes() -> None: content = """ #include #include "local.h" # include "space.h" """ data = parse_string(content, cleandoc=True) assert data == ParsedData( includes=[Include(""), Include('"local.h"'), Include('"space.h"')] ) def test_pragma() -> None: content = """ #pragma once """ data = parse_string(content, cleandoc=True) assert data == ParsedData( pragmas=[Pragma(content=Value(tokens=[Token(value="once")]))] ) def test_pragma_more() -> None: content = """ #pragma (some content here) #pragma (even \ more \ content here) """ data = parse_string(content, cleandoc=True) assert data == ParsedData( pragmas=[ Pragma( content=Value( tokens=[ Token(value="("), Token(value="some"), Token(value="content"), Token(value="here"), Token(value=")"), ] ) ), Pragma( content=Value( tokens=[ Token(value="("), Token(value="even"), Token(value="more"), Token(value="content"), Token(value="here"), Token(value=")"), ] ) ), ] ) def test_line_and_define() -> None: content = """ // this should work + change line number of error #line 40 "filename.h" // this should fail #define 1 """ with pytest.raises(CxxParseError) as e: parse_string(content, cleandoc=True) assert "filename.h:41" in str(e.value) # # extern "C" # def test_extern_c() -> None: content = """ extern "C" { int x; }; int y; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ), Variable( name=PQName(segments=[NameSpecifier(name="y")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ), ] ) ) def test_misc_extern_inline() -> None: content = """ extern "C++" { inline HAL_Value HAL_GetSimValue(HAL_SimValueHandle handle) { HAL_Value v; return v; } } // extern "C++" """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[NameSpecifier(name="HAL_Value")]) ), name=PQName(segments=[NameSpecifier(name="HAL_GetSimValue")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="HAL_SimValueHandle")] ) ), name="handle", ) ], inline=True, has_body=True, ) ] ) ) # # Misc # def test_static_assert_1() -> None: # static_assert should be ignored content = """ static_assert(x == 1); """ data = parse_string(content, cleandoc=True) assert data == ParsedData() def test_static_assert_2() -> None: # static_assert should be ignored content = """ static_assert(sizeof(int) == 4, "integer size is wrong" "for some reason"); """ data = parse_string(content, cleandoc=True) assert data == ParsedData() def test_comment_eof() -> None: content = """ namespace a {} // namespace a""" data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope(namespaces={"a": NamespaceScope(name="a")}) ) def test_final() -> None: content = """ // ok here int fn(const int final); // ok here int final = 2; // but it's a keyword here struct B final : A {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="B")], classkey="struct" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="A")]), ) ], final=True, ) ) ], functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ), const=True, ), name="final", ) ], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="final")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="2")]), ) ], ) ) # # User defined literals # def test_user_defined_literal() -> None: content = """ units::volt_t v = 1_V; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="v")]), type=Type( typename=PQName( segments=[ NameSpecifier(name="units"), NameSpecifier(name="volt_t"), ] ) ), value=Value(tokens=[Token(value="1_V")]), ) ] ) ) # # Line continuation # def test_line_continuation() -> None: content = """ static int \ variable; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="variable")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), static=True, ) ] ) ) # # #warning (C++23) # def test_warning_directive() -> None: content = """ #warning "this is a warning" """ data = parse_string(content, cleandoc=True) assert data == ParsedData() cxxheaderparser-1.3.1/tests/test_namespaces.py000066400000000000000000000133761455035044000216100ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( ForwardDecl, FundamentalSpecifier, NamespaceAlias, NameSpecifier, PQName, Token, Type, Value, Variable, ) from cxxheaderparser.simple import ( NamespaceScope, parse_string, ParsedData, ) import pytest import re def test_dups_in_different_ns() -> None: content = """ namespace { int x = 4; } int x = 5; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="5")]), ) ], namespaces={ "": NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), value=Value(tokens=[Token(value="4")]), ) ] ) }, ) ) def test_correct_ns() -> None: content = """ namespace a::b::c { int i1; } namespace a { namespace b { namespace c { int i2; } } } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( namespaces={ "a": NamespaceScope( name="a", namespaces={ "b": NamespaceScope( name="b", namespaces={ "c": NamespaceScope( name="c", variables=[ Variable( name=PQName( segments=[NameSpecifier(name="i1")] ), type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), ), Variable( name=PQName( segments=[NameSpecifier(name="i2")] ), type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), ), ], ) }, ) }, ) } ) ) def test_inline_namespace() -> None: content = """ namespace Lib { inline namespace Lib_1 { class A; } } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( namespaces={ "Lib": NamespaceScope( name="Lib", namespaces={ "Lib_1": NamespaceScope( name="Lib_1", inline=True, forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class", ) ) ], ) }, ) } ) ) def test_invalid_inline_namespace() -> None: content = """ inline namespace a::b {} """ err = ":1: parse error evaluating 'inline': a nested namespace definition cannot be inline" with pytest.raises(CxxParseError, match=re.escape(err)): parse_string(content, cleandoc=True) def test_ns_alias() -> None: content = """ namespace ANS = my::ns; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( ns_alias=[NamespaceAlias(alias="ANS", names=["my", "ns"])] ) ) def test_ns_alias_global() -> None: content = """ namespace ANS = ::my::ns; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( ns_alias=[NamespaceAlias(alias="ANS", names=["::", "my", "ns"])] ) ) cxxheaderparser-1.3.1/tests/test_numeric_literals.py000066400000000000000000000034541455035044000230260ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( FundamentalSpecifier, NameSpecifier, PQName, Token, Type, Value, Variable, ) from cxxheaderparser.simple import ( Include, NamespaceScope, Pragma, parse_string, ParsedData, ) def test_numeric_literals() -> None: content = """ #pragma once #include int test_binary = 0b01'10'01; int test_decimal = 123'456'789u; int test_octal = 012'42'11l; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="test_binary")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="0b01'10'01")]), ), Variable( name=PQName(segments=[NameSpecifier(name="test_decimal")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="123'456'789u")]), ), Variable( name=PQName(segments=[NameSpecifier(name="test_octal")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="012'42'11l")]), ), ] ), pragmas=[Pragma(content=Value(tokens=[Token(value="once")]))], includes=[Include(filename="")], ) cxxheaderparser-1.3.1/tests/test_operators.py000066400000000000000000000756211455035044000215100ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( ClassDecl, Function, FundamentalSpecifier, Method, MoveReference, NameSpecifier, Pointer, PQName, Parameter, Reference, Type, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_class_operators() -> None: content = r""" class OperatorClass { public: void operator=(const Sample25Class &); void operator-=(const Sample25Class &); void operator+=(); void operator[](); bool operator==(const int &b); OperatorClass &operator+(); void operator-(); void operator*(); void operator\(); void operator%(); void operator^(); void operator|(); void operator&(); void operator~(); void operator<<(); void operator>>(); void operator!=(); void operator<(); void operator>(); void operator>=(); void operator<=(); void operator!(); void operator&&(); void operator||(); void operator+=(); void operator-=(); void operator*=(); void operator\=(); void operator%=(); void operator&=(); void operator|=(); void operator^=(); void operator<<=(); void operator>>=(); void operator++(); void operator--(); void operator()(); void operator->(); void operator,(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="OperatorClass")], classkey="class", ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator=")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="Sample25Class") ] ), const=True, ) ) ) ], access="public", operator="=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator-=")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="Sample25Class") ] ), const=True, ) ) ) ], access="public", operator="-=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator+=")]), parameters=[], access="public", operator="+=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator[]")]), parameters=[], access="public", operator="[]", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="bool")] ) ), name=PQName(segments=[NameSpecifier(name="operator==")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ), const=True, ) ), name="b", ) ], access="public", operator="==", ), Method( return_type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="OperatorClass")] ) ) ), name=PQName(segments=[NameSpecifier(name="operator+")]), parameters=[], access="public", operator="+", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator-")]), parameters=[], access="public", operator="-", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator*")]), parameters=[], access="public", operator="*", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator\\")]), parameters=[], access="public", operator="\\", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator%")]), parameters=[], access="public", operator="%", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator^")]), parameters=[], access="public", operator="^", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator|")]), parameters=[], access="public", operator="|", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator&")]), parameters=[], access="public", operator="&", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator~")]), parameters=[], access="public", operator="~", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator<<")]), parameters=[], access="public", operator="<<", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator>>")]), parameters=[], access="public", operator=">>", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator!=")]), parameters=[], access="public", operator="!=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator<")]), parameters=[], access="public", operator="<", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator>")]), parameters=[], access="public", operator=">", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator>=")]), parameters=[], access="public", operator=">=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator<=")]), parameters=[], access="public", operator="<=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator!")]), parameters=[], access="public", operator="!", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator&&")]), parameters=[], access="public", operator="&&", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator||")]), parameters=[], access="public", operator="||", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator+=")]), parameters=[], access="public", operator="+=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator-=")]), parameters=[], access="public", operator="-=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator*=")]), parameters=[], access="public", operator="*=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator\\=")]), parameters=[], access="public", operator="\\=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator%=")]), parameters=[], access="public", operator="%=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator&=")]), parameters=[], access="public", operator="&=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator|=")]), parameters=[], access="public", operator="|=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator^=")]), parameters=[], access="public", operator="^=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator<<=")]), parameters=[], access="public", operator="<<=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator>>=")]), parameters=[], access="public", operator=">>=", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator++")]), parameters=[], access="public", operator="++", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator--")]), parameters=[], access="public", operator="--", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator()")]), parameters=[], access="public", operator="()", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator->")]), parameters=[], access="public", operator="->", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="operator,")]), parameters=[], access="public", operator=",", ), ], ) ] ) ) def test_conversion_operators() -> None: content = """ class Foo { public: operator Type1() const { return SomeMethod(); } explicit operator Type2() const; virtual operator bool() const; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Foo")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName(segments=[NameSpecifier(name="Type1")]) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], has_body=True, access="public", const=True, operator="conversion", ), Method( return_type=Type( typename=PQName(segments=[NameSpecifier(name="Type2")]) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], access="public", const=True, explicit=True, operator="conversion", ), Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="bool")] ) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], access="public", const=True, virtual=True, operator="conversion", ), ], ) ] ) ) def test_conversion_operators_decorated() -> None: content = """ struct S { operator const native_handle_t*() const; operator const native_handle_t&() const; operator const native_handle_t&&() const; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ) ), methods=[ Method( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="native_handle_t")] ), const=True, ) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], access="public", const=True, operator="conversion", ), Method( return_type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="native_handle_t")] ), const=True, ) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], access="public", const=True, operator="conversion", ), Method( return_type=MoveReference( moveref_to=Type( typename=PQName( segments=[NameSpecifier(name="native_handle_t")] ), const=True, ) ), name=PQName(segments=[NameSpecifier(name="operator")]), parameters=[], access="public", const=True, operator="conversion", ), ], ) ] ) ) def test_free_operator() -> None: content = """ std::ostream& operator<<(std::ostream& os, const MyDate& dt); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="ostream"), ] ) ) ), name=PQName(segments=[NameSpecifier(name="operator<<")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="ostream"), ] ) ) ), name="os", ), Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="MyDate")] ), const=True, ) ), name="dt", ), ], operator="<<", ) ] ) ) cxxheaderparser-1.3.1/tests/test_preprocessor.py000066400000000000000000000133711455035044000222120ustar00rootroot00000000000000import os import pathlib import pytest import re import shutil import subprocess import typing from cxxheaderparser.options import ParserOptions, PreprocessorFunction from cxxheaderparser import preprocessor from cxxheaderparser.simple import ( NamespaceScope, ParsedData, parse_file, parse_string, Include, ) from cxxheaderparser.types import ( FundamentalSpecifier, NameSpecifier, PQName, Token, Type, Value, Variable, ) @pytest.fixture(params=["gcc", "msvc", "pcpp"]) def make_pp(request) -> typing.Callable[..., PreprocessorFunction]: param = request.param if param == "gcc": gcc_path = shutil.which("g++") if not gcc_path: pytest.skip("g++ not found") subprocess.run([gcc_path, "--version"]) return preprocessor.make_gcc_preprocessor elif param == "msvc": gcc_path = shutil.which("cl.exe") if not gcc_path: pytest.skip("cl.exe not found") return preprocessor.make_msvc_preprocessor elif param == "pcpp": if preprocessor.pcpp is None: pytest.skip("pcpp not installed") return preprocessor.make_pcpp_preprocessor else: assert False def test_basic_preprocessor( make_pp: typing.Callable[..., PreprocessorFunction] ) -> None: content = """ #define X 1 int x = X; """ options = ParserOptions(preprocessor=make_pp()) data = parse_string(content, cleandoc=True, options=options) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="1")]), ) ] ) ) def test_preprocessor_omit_content( make_pp: typing.Callable[..., PreprocessorFunction], tmp_path: pathlib.Path, ) -> None: """Ensure that content in other headers is omitted""" h_content = '#include "t2.h"' "\n" "int x = X;\n" h2_content = "#define X 2\n" "int omitted = 1;\n" with open(tmp_path / "t1.h", "w") as fp: fp.write(h_content) with open(tmp_path / "t2.h", "w") as fp: fp.write(h2_content) options = ParserOptions(preprocessor=make_pp()) data = parse_file(tmp_path / "t1.h", options=options) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="2")]), ) ] ) ) def test_preprocessor_omit_content2( make_pp: typing.Callable[..., PreprocessorFunction], tmp_path: pathlib.Path, ) -> None: """ Ensure that content in other headers is omitted while handling pcpp relative path quirk """ h_content = '#include "t2.h"' "\n" "int x = X;\n" h2_content = "#define X 2\n" "int omitted = 1;\n" tmp_path2 = tmp_path / "l1" tmp_path2.mkdir() with open(tmp_path2 / "t1.h", "w") as fp: fp.write(h_content) with open(tmp_path2 / "t2.h", "w") as fp: fp.write(h2_content) options = ParserOptions(preprocessor=make_pp(include_paths=[str(tmp_path)])) # Weirdness happens here os.chdir(tmp_path) data = parse_file(tmp_path2 / "t1.h", options=options) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="2")]), ) ] ) ) def test_preprocessor_encoding( make_pp: typing.Callable[..., PreprocessorFunction], tmp_path: pathlib.Path ) -> None: """Ensure we can handle alternate encodings""" h_content = b"// \xa9 2023 someone\n" b'#include "t2.h"' b"\n" b"int x = X;\n" h2_content = b"// \xa9 2023 someone\n" b"#define X 3\n" b"int omitted = 1;\n" with open(tmp_path / "t1.h", "wb") as fp: fp.write(h_content) with open(tmp_path / "t2.h", "wb") as fp: fp.write(h2_content) options = ParserOptions(preprocessor=make_pp(encoding="cp1252")) data = parse_file(tmp_path / "t1.h", options=options, encoding="cp1252") assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="3")]), ) ] ) ) @pytest.mark.skipif(preprocessor.pcpp is None, reason="pcpp not installed") def test_preprocessor_passthru_includes(tmp_path: pathlib.Path) -> None: """Ensure that all #include pass through""" h_content = '#include "t2.h"\n' with open(tmp_path / "t1.h", "w") as fp: fp.write(h_content) with open(tmp_path / "t2.h", "w") as fp: fp.write("") options = ParserOptions( preprocessor=preprocessor.make_pcpp_preprocessor( passthru_includes=re.compile(".+") ) ) data = parse_file(tmp_path / "t1.h", options=options) assert data == ParsedData( namespace=NamespaceScope(), includes=[Include(filename='"t2.h"')] ) cxxheaderparser-1.3.1/tests/test_skip.py000066400000000000000000000132751455035044000204350ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` # .. and modified import inspect import typing from cxxheaderparser.parser import CxxParser from cxxheaderparser.simple import ( ClassScope, NamespaceScope, ParsedData, SClassBlockState, SExternBlockState, SNamespaceBlockState, SimpleCxxVisitor, ) from cxxheaderparser.types import ( ClassDecl, Function, FundamentalSpecifier, Method, NameSpecifier, PQName, Type, ) # # ensure extern block is skipped # class SkipExtern(SimpleCxxVisitor): def on_extern_block_start(self, state: SExternBlockState) -> typing.Optional[bool]: return False def test_skip_extern(): content = """ void fn1(); extern "C" { void fn2(); } void fn3(); """ v = SkipExtern() parser = CxxParser("", inspect.cleandoc(content), v) parser.parse() data = v.data assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[], ), ] ) ) # # ensure class block is skipped # class SkipClass(SimpleCxxVisitor): def on_class_start(self, state: SClassBlockState) -> typing.Optional[bool]: if getattr(state.class_decl.typename.segments[0], "name", None) == "Skip": return False return super().on_class_start(state) def test_skip_class() -> None: content = """ void fn1(); class Skip { void fn2(); }; class Yup { void fn3(); }; void fn5(); """ v = SkipClass() parser = CxxParser("", inspect.cleandoc(content), v) parser.parse() data = v.data assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Yup")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn3")]), parameters=[], access="private", ) ], ), ], functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn5")]), parameters=[], ), ], ) ) # # ensure namespace 'skip' is skipped # class SkipNamespace(SimpleCxxVisitor): def on_namespace_start(self, state: SNamespaceBlockState) -> typing.Optional[bool]: if "skip" in state.namespace.names[0]: return False return super().on_namespace_start(state) def test_skip_namespace(): content = """ void fn1(); namespace skip { void fn2(); namespace thistoo { void fn3(); } } namespace ok { void fn4(); } void fn5(); """ v = SkipNamespace() parser = CxxParser("", inspect.cleandoc(content), v) parser.parse() data = v.data assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn1")]), parameters=[], ), Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn5")]), parameters=[], ), ], namespaces={ "ok": NamespaceScope( name="ok", functions=[ Function( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn4")]), parameters=[], ) ], ), }, ) ) cxxheaderparser-1.3.1/tests/test_template.py000066400000000000000000003446141455035044000213060ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( Array, BaseClass, ClassDecl, DecltypeSpecifier, DeductionGuide, Field, ForwardDecl, Function, FunctionType, FundamentalSpecifier, Method, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateInst, TemplateNonTypeParam, TemplateSpecialization, TemplateTypeParam, Token, Type, Value, Variable, ) from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string def test_template_base_template_ns() -> None: content = """ class A : public B::C {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="B", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), ] ), ), NameSpecifier(name="C"), ] ), ) ], ) ) ] ) ) def test_template_non_type_various() -> None: content = """ // simple non-type template parameter template struct S { int a[N]; }; template struct S2 {}; // complicated non-type example template struct Complicated { // calls the function selected at compile time // and stores the result in the array selected at compile time void foo(char base) { ra[4] = pf(c - base); } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ), template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="N", ) ] ), ), fields=[ Field( name="a", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="N")]), ), access="public", ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S2")], classkey="struct" ), template=TemplateDecl( params=[ TemplateNonTypeParam( type=Pointer( ptr_to=Type( typename=PQName( segments=[ FundamentalSpecifier(name="char") ] ), const=True, ) ) ) ] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Complicated")], classkey="struct", ), template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), name="c", ), TemplateNonTypeParam( type=Reference( ref_to=Array( array_of=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), size=Value(tokens=[Token(value="5")]), ) ), name="ra", ), TemplateNonTypeParam( type=Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ], ) ), name="pf", ), ] ), ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="foo")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), name="base", ) ], has_body=True, access="public", ) ], ), ] ) ) def test_template_dependent_nontype_default() -> None: content = """ template class X; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="class" ), template=TemplateDecl( params=[ TemplateTypeParam(typekey="class", name="T"), TemplateNonTypeParam( type=Type( typename=PQName( segments=[ NameSpecifier(name="T"), NameSpecifier(name="type"), ] ) ), name="n", default=Value(tokens=[Token(value="0")]), ), ] ), ) ] ) ) def test_template_optional_names() -> None: content = """ template class My_vector; template struct My_op_functor; template class My_tuple; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="My_vector")], classkey="class" ), template=TemplateDecl(params=[TemplateTypeParam(typekey="class")]), ), ForwardDecl( typename=PQName( segments=[NameSpecifier(name="My_op_functor")], classkey="struct", ), template=TemplateDecl( params=[ TemplateTypeParam( typekey="class", default=Value(tokens=[Token(value="void")]), ) ] ), ), ForwardDecl( typename=PQName( segments=[NameSpecifier(name="My_tuple")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", param_pack=True)] ), ), ] ) ) def test_template_template_template() -> None: content = """ template struct eval; // primary template template class TT, typename T1, typename... Rest> struct eval> {}; // partial specialization of eval """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier( name="eval", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="TT", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="T1" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Rest" ) ] ) ), param_pack=True, ), ] ), ) ] ) ) ) ] ), ) ], classkey="struct", ), template=TemplateDecl( params=[ TemplateTypeParam( typekey="class", name="TT", template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename"), TemplateTypeParam( typekey="typename", param_pack=True ), ] ), ), TemplateTypeParam(typekey="typename", name="T1"), TemplateTypeParam( typekey="typename", name="Rest", param_pack=True ), ] ), ) ) ], forward_decls=[ ForwardDecl( typename=PQName( segments=[NameSpecifier(name="eval")], classkey="struct" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ], ) ) def test_template_static_var() -> None: content = """ template struct X { static int x; }; template int X::x = 5; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", static=True, ) ], ) ], variables=[ Variable( name=PQName( segments=[ NameSpecifier( name="X", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[NameSpecifier(name="T")] ) ) ) ] ), ), NameSpecifier(name="x"), ] ), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="5")]), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ], ) ) def test_template_fn_template() -> None: content = """ class S { template StringRef copy(Allocator &A) const { // Don't request a length 0 copy from the allocator. if (empty()) return StringRef(); char *S = A.template Allocate(Length); std::copy(begin(), end(), S); return StringRef(S, Length); } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="class" ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[NameSpecifier(name="StringRef")] ) ), name=PQName(segments=[NameSpecifier(name="copy")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="Allocator") ] ) ) ), name="A", ) ], has_body=True, template=TemplateDecl( params=[ TemplateTypeParam( typekey="typename", name="Allocator" ) ] ), access="private", const=True, ) ], ) ] ) ) def test_template_fn_param_initializer() -> None: content = """ template void fn(something s = something{1, 2, 3}); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( functions=[ Function( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="void")]) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[ NameSpecifier( name="something", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="T" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="U" ) ] ) ) ), ] ), ) ] ) ), name="s", default=Value( tokens=[ Token(value="something"), Token(value="<"), Token(value="T"), Token(value=","), Token(value="U"), Token(value=">"), Token(value="{"), Token(value="1"), Token(value=","), Token(value="2"), Token(value=","), Token(value="3"), Token(value="}"), ] ), ) ], template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename", name="T"), TemplateTypeParam(typekey="typename", name="U"), ] ), ) ] ) ) def test_template_huge() -> None: content = """ // clang-format off class AlmondClass { public: std::map > > meth(bool flag, std::map > > bigArg); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="AlmondClass")], classkey="class", ) ), methods=[ Method( return_type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="map", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="unsigned" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="std" ), NameSpecifier( name="pair", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="unsigned" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="SnailTemplateClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="SnailNamespace" ), NameSpecifier( name="SnailClass" ), ] ) ) ) ] ), ) ] ) ) ), ] ), ), ] ) ) ), ] ), ), ] ) ), name=PQName(segments=[NameSpecifier(name="meth")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="bool")] ) ), name="flag", ), Parameter( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="map", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="unsigned" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="std" ), NameSpecifier( name="pair", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="unsigned" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="SnailTemplateClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="SnailNamespace" ), NameSpecifier( name="SnailClass" ), ] ) ) ) ] ), ) ] ) ) ), ] ), ), ] ) ) ), ] ), ), ] ) ), name="bigArg", ), ], access="public", ) ], ) ] ) ) def test_template_specialized() -> None: content = """ template <> class FruitFly : public Fly {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier( name="FruitFly", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ] ), ) ], classkey="class", ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="Fly")]), ) ], template=TemplateDecl(), ) ) ] ) ) def test_template_class_defaults() -> None: content = """ template > class Raddish_SetIterator : public Raddish_Iterator { protected: VALUE_SET_ITERATOR _beg, _end; public: Raddish_SetIterator(const VALUE_SET_ITERATOR &begin, const VALUE_SET_ITERATOR &end) { init(begin, end); } }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Raddish_SetIterator")], classkey="class", ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="Raddish_Iterator", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="VALUE" ) ] ) ) ) ] ), ) ] ), ) ], template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename", name="VALUE"), TemplateTypeParam( typekey="typename", name="VALUE_SET_ITERATOR" ), TemplateTypeParam( typekey="typename", name="ACCESSOR", default=Value( tokens=[ Token(value="Raddish"), Token(value="::"), Token(value="SimpleAccessor"), Token(value="<"), Token(value="VALUE"), Token(value=","), Token(value="VALUE_SET_ITERATOR"), Token(value=">"), ] ), ), ] ), ), fields=[ Field( access="protected", type=Type( typename=PQName( segments=[NameSpecifier(name="VALUE_SET_ITERATOR")] ) ), name="_beg", ), Field( access="protected", type=Type( typename=PQName( segments=[NameSpecifier(name="VALUE_SET_ITERATOR")] ) ), name="_end", ), ], methods=[ Method( return_type=None, name=PQName( segments=[NameSpecifier(name="Raddish_SetIterator")] ), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier( name="VALUE_SET_ITERATOR" ) ] ), const=True, ) ), name="begin", ), Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier( name="VALUE_SET_ITERATOR" ) ] ), const=True, ) ), name="end", ), ], has_body=True, access="public", constructor=True, ) ], ) ] ) ) def test_template_many_packs() -> None: content = """ // clang-format off template class XYZ : public MyBaseClass { public: XYZ(); }; template class concat_iterator : public iterator_facade_base, std::forward_iterator_tag, ValueT> { }; template struct build_index_impl : build_index_impl {}; template struct build_index_impl<0, I...> : index_sequence {}; template struct is_callable, void_t()).*std::declval())(std::declval()...))>> : std::true_type {}; template struct S : public T... {}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="XYZ")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="MyBaseClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Type" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ), ] ), ) ] ), ) ], template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="Type")] ), ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="XYZ")]), parameters=[], access="public", constructor=True, ) ], ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="concat_iterator")], classkey="class", ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="iterator_facade_base", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="concat_iterator", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="ValueT" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="IterTs" ) ] ) ), param_pack=True, ), ] ), ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="std" ), NameSpecifier( name="forward_iterator_tag" ), ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="ValueT" ) ] ) ) ), ] ), ) ] ), ) ], template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename", name="ValueT"), TemplateTypeParam( typekey="typename", name="IterTs", param_pack=True ), ] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="build_index_impl")], classkey="struct", ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="build_index_impl", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value( tokens=[ Token(value="N"), Token(value="-"), Token(value="1"), ] ) ), TemplateArgument( arg=Value( tokens=[ Token(value="N"), Token(value="-"), Token(value="1"), ] ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="I" ) ] ) ), param_pack=True, ), ] ), ) ] ), ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="N", ), TemplateNonTypeParam( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="I", param_pack=True, ), ] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier( name="build_index_impl", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="0")]) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="I") ] ) ), param_pack=True, ), ] ), ) ], classkey="struct", ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier( name="index_sequence", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="I" ) ] ) ), param_pack=True, ) ] ), ) ] ), ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="size_t"), ] ) ), name="I", param_pack=True, ) ] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier( name="is_callable", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="F") ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="P") ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="typelist", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="T" ) ] ) ), param_pack=True, ) ] ), ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="void_t", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ DecltypeSpecifier( tokens=[ Token( value="(" ), Token( value="(" ), Token( value="*" ), Token( value="std" ), Token( value="::" ), Token( value="declval" ), Token( value="<" ), Token( value="P" ), Token( value=">" ), Token( value="(" ), Token( value=")" ), Token( value=")" ), Token( value="." ), Token( value="*" ), Token( value="std" ), Token( value="::" ), Token( value="declval" ), Token( value="<" ), Token( value="F" ), Token( value=">" ), Token( value="(" ), Token( value=")" ), Token( value=")" ), Token( value="(" ), Token( value="std" ), Token( value="::" ), Token( value="declval" ), Token( value="<" ), Token( value="T" ), Token( value=">" ), Token( value="(" ), Token( value=")" ), Token( value="..." ), Token( value=")" ), ] ) ] ) ) ) ] ), ) ] ) ) ), ] ), ) ], classkey="struct", ), bases=[ BaseClass( access="public", typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="true_type"), ] ), ) ], template=TemplateDecl( params=[ TemplateTypeParam(typekey="typename", name="F"), TemplateTypeParam(typekey="typename", name="P"), TemplateTypeParam( typekey="typename", name="T", param_pack=True ), ] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="S")], classkey="struct" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="T")]), param_pack=True, ) ], template=TemplateDecl( params=[ TemplateNonTypeParam( type=Type( typename=PQName( segments=[NameSpecifier(name="T")] ) ), param_pack=True, ) ] ), ) ), ] ) ) def test_template_specialized_fn_typename() -> None: content = """ // clang-format off struct T{}; template class C { template<> void foo(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="T")], classkey="struct" ) ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="T"), ], has_typename=True, ) ), ) ] ), ) ] ), parameters=[], template=TemplateDecl(), access="private", ) ], ), ] ) ) def test_template_specialized_fn_typename_template() -> None: content = """ // clang-format off template struct T{}; template class C { template<> void foo>(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="T")], classkey="struct" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="X")] ), ) ), ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="C")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier( name="T", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ] ), ), ], has_typename=True, ) ), ) ] ), ) ] ), parameters=[], template=TemplateDecl(), access="private", ) ], ), ] ) ) def test_template_instantiation() -> None: content = """ template class MyClass<1,2>; template class __attribute__(("something")) MyClass<3,4>; namespace foo { template class MyClass<5,6>; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( template_insts=[ TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="1")]) ), TemplateArgument( arg=Value(tokens=[Token(value="2")]) ), ] ), ) ] ), extern=False, ), TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="3")]) ), TemplateArgument( arg=Value(tokens=[Token(value="4")]) ), ] ), ) ] ), extern=False, ), ], namespaces={ "foo": NamespaceScope( name="foo", template_insts=[ TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="5")]) ), TemplateArgument( arg=Value(tokens=[Token(value="6")]) ), ] ), ) ] ), extern=False, ) ], ) }, ) ) def test_extern_template() -> None: content = """ extern template class MyClass<1,2>; extern template class __attribute__(("something")) MyClass<3,4>; namespace foo { extern template class MyClass<5,6>; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( template_insts=[ TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="1")]) ), TemplateArgument( arg=Value(tokens=[Token(value="2")]) ), ] ), ) ] ), extern=True, ), TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="3")]) ), TemplateArgument( arg=Value(tokens=[Token(value="4")]) ), ] ), ) ] ), extern=True, ), ], namespaces={ "foo": NamespaceScope( name="foo", template_insts=[ TemplateInst( typename=PQName( segments=[ NameSpecifier( name="MyClass", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Value(tokens=[Token(value="5")]) ), TemplateArgument( arg=Value(tokens=[Token(value="6")]) ), ] ), ) ] ), extern=True, ) ], ) }, ) ) def test_fwd_declared_method() -> None: content = """ // forward declaration for specialized template function template <> Clazz::Clazz(); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( method_impls=[ Method( return_type=None, name=PQName( segments=[ NameSpecifier( name="Clazz", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[NameSpecifier(name="A")] ) ) ) ] ), ), NameSpecifier(name="Clazz"), ] ), parameters=[], template=TemplateDecl(), constructor=True, ) ] ) ) def test_multiple_explicit_member_specialization() -> None: content = """ template <> template <> inline Standard_CString StdObjMgt_Attribute::Simple::PName() const { return "PDF_TagSource"; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( method_impls=[ Method( return_type=Type( typename=PQName( segments=[NameSpecifier(name="Standard_CString")] ) ), name=PQName( segments=[ NameSpecifier( name="StdObjMgt_Attribute", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="TDF_TagSource" ) ] ) ) ) ] ), ), NameSpecifier( name="Simple", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Standard_Integer" ) ] ) ) ) ] ), ), NameSpecifier(name="PName"), ] ), parameters=[], inline=True, has_body=True, template=[TemplateDecl(), TemplateDecl()], const=True, ) ] ) ) def test_member_class_template_specialization() -> None: content = """ template <> // specialization of a member class template template struct A::C { void f(); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[ NameSpecifier( name="A", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="char" ) ] ) ) ) ] ), ), NameSpecifier(name="C"), ], classkey="struct", ), template=[ TemplateDecl(), TemplateDecl( params=[TemplateTypeParam(typekey="class", name="U")] ), ], ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="f")]), parameters=[], access="public", ) ], ) ] ) ) def test_template_deduction_guide() -> None: content = """ template > Error(std::basic_string_view) -> Error; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( deduction_guides=[ DeductionGuide( result_type=Type( typename=PQName( segments=[ NameSpecifier( name="Error", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="string" ), ] ) ) ) ] ), ) ] ) ), name=PQName(segments=[NameSpecifier(name="Error")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="basic_string_view", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="CharT" ) ] ) ) ), TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="Traits" ) ] ) ) ), ] ), ), ] ) ) ) ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_tokfmt.py000066400000000000000000000030131455035044000207600ustar00rootroot00000000000000import pytest from cxxheaderparser.lexer import PlyLexer, LexerTokenStream from cxxheaderparser.tokfmt import tokfmt from cxxheaderparser.types import Token @pytest.mark.parametrize( "instr", [ "int", "unsigned int", "::uint8_t", "void *", "void * *", "const char *", "const char[]", "void * (*)()", "void (*)(void * buf, int buflen)", "void (* fnType)(void * buf, int buflen)", "TypeName& x", "vector&", "std::vector *", "Alpha::Omega", "Convoluted::Nested::Mixin", "std::function", "std::shared_ptr>", "tr1::shared_ptr>", "std::map>>", "std::is_base_of::value", "const char&&", "something{1, 2, 3}", "operator-=", "operator[]", "operator*", "operator>=", ], ) def test_tokfmt(instr: str) -> None: """ Each input string is exactly what the output of tokfmt should be """ toks = [] lexer = PlyLexer("") lexer.input(instr) while True: tok = lexer.token() if not tok: break if tok.type not in LexerTokenStream._discard_types: toks.append(Token(tok.value, tok.type)) assert tokfmt(toks) == instr cxxheaderparser-1.3.1/tests/test_typedef.py000066400000000000000000000745701455035044000211340ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( AnonymousName, Array, BaseClass, ClassDecl, EnumDecl, Enumerator, Field, FunctionType, FundamentalSpecifier, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateSpecialization, Token, Type, Typedef, Value, ) from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string def test_simple_typedef() -> None: content = """ typedef std::vector IntVector; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="vector", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ] ), ), ] ) ), name="IntVector", ) ] ) ) def test_struct_typedef_1() -> None: content = """ typedef struct { int m; } unnamed_struct, *punnamed_struct; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), fields=[ Field( name="m", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", ) ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="unnamed_struct", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ) ), name="punnamed_struct", ), ], ) ) def test_struct_typedef_2() -> None: content = """ typedef struct { int m; } * punnamed_struct, unnamed_struct; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), fields=[ Field( name="m", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", ) ], ) ], typedefs=[ Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ) ), name="punnamed_struct", ), Typedef( type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="unnamed_struct", ), ], ) ) def test_typedef_array() -> None: content = """ typedef char TenCharArray[10]; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), size=Value(tokens=[Token(value="10")]), ), name="TenCharArray", ) ] ) ) def test_typedef_array_of_struct() -> None: content = """ typedef struct{} tx[3], ty; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ) ) ], typedefs=[ Typedef( type=Array( array_of=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), size=Value(tokens=[Token(value="3")]), ), name="tx", ), Typedef( type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="ty", ), ], ) ) def test_typedef_class_w_base() -> None: content = """ typedef class XX : public F {} G; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="XX")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName(segments=[NameSpecifier(name="F")]), ) ], ) ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="XX")], classkey="class" ) ), name="G", ) ], ) ) def test_complicated_typedef() -> None: content = """ typedef int int_t, *intp_t, (&fp)(int, ulong), arr_t[10]; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name="int_t", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ), name="intp_t", ), Typedef( type=Reference( ref_to=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="ulong")] ) ) ), ], ) ), name="fp", ), Typedef( type=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="10")]), ), name="arr_t", ), ] ) ) def test_typedef_c_struct_idiom() -> None: content = """ // common C idiom to avoid having to write "struct S" typedef struct {int a; int b;} S, *pS; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), fields=[ Field( name="a", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", ), Field( name="b", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), access="public", ), ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="S", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ) ), name="pS", ), ], ) ) def test_typedef_struct_same_name() -> None: content = """ typedef struct Fig { int a; } Fig; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Fig")], classkey="struct" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="a", ) ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="Fig")], classkey="struct" ) ), name="Fig", ) ], ) ) def test_typedef_struct_w_enum() -> None: content = """ typedef struct { enum BeetEnum : int { FAIL = 0, PASS = 1 }; } BeetStruct; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="BeetEnum")], classkey="enum", ), values=[ Enumerator( name="FAIL", value=Value(tokens=[Token(value="0")]) ), Enumerator( name="PASS", value=Value(tokens=[Token(value="1")]) ), ], base=PQName(segments=[FundamentalSpecifier(name="int")]), access="public", ) ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="struct" ) ), name="BeetStruct", ) ], ) ) def test_typedef_union() -> None: content = """ typedef union apricot_t { int i; float f; char s[20]; } Apricot; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="apricot_t")], classkey="union" ) ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="i", ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="float")] ) ), name="f", ), Field( access="public", type=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), size=Value(tokens=[Token(value="20")]), ), name="s", ), ], ) ], typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="apricot_t")], classkey="union" ) ), name="Apricot", ) ], ) ) def test_typedef_fnptr() -> None: content = """ typedef void *(*fndef)(int); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Pointer( ptr_to=FunctionType( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], ) ), name="fndef", ) ] ) ) def test_typedef_const() -> None: content = """ typedef int theint, *const ptheint; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), name="theint", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), const=True, ), name="ptheint", ), ] ) ) def test_enum_typedef_1() -> None: content = """ typedef enum {} E; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[], ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="E", ) ], ) ) def test_enum_typedef_2() -> None: content = """ typedef enum { E1 } BE; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[Enumerator(name="E1")], ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="BE", ) ], ) ) def test_enum_typedef_3() -> None: content = """ typedef enum { E1, E2, } E; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[Enumerator(name="E1"), Enumerator(name="E2")], ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="E", ) ], ) ) def test_enum_typedef_3_1() -> None: content = """ typedef enum { E1 } * PBE; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[Enumerator(name="E1")], ) ], typedefs=[ Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ) ) ), name="PBE", ) ], ) ) def test_enum_typedef_4() -> None: content = """ typedef enum { E1 } * PBE, BE; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[Enumerator(name="E1")], ) ], typedefs=[ Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ) ) ), name="PBE", ), Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="BE", ), ], ) ) def test_enum_typedef_5() -> None: content = """ typedef enum { E1 } BE, *PBE; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[Enumerator(name="E1")], ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="BE", ), Typedef( type=Pointer( ptr_to=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="enum" ) ) ), name="PBE", ), ], ) ) def test_enum_typedef_fwd() -> None: content = """ typedef enum BE BET; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Type( typename=PQName( segments=[NameSpecifier(name="BE")], classkey="enum" ) ), name="BET", ) ] ) ) def test_typedef_enum_expr() -> None: content = """ typedef enum { StarFruit = (2 + 2) / 2 } Carambola; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum"), values=[ Enumerator( name="StarFruit", value=Value( tokens=[ Token(value="("), Token(value="2"), Token(value="+"), Token(value="2"), Token(value=")"), Token(value="/"), Token(value="2"), ] ), ) ], ) ], typedefs=[ Typedef( type=Type( typename=PQName(segments=[AnonymousName(id=1)], classkey="enum") ), name="Carambola", ) ], ) ) def test_volatile_typedef() -> None: content = """ typedef volatile signed short vint16; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( typedefs=[ Typedef( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="signed short")] ), volatile=True, ), name="vint16", ) ] ) ) def test_function_typedef() -> None: content = """ typedef void fn(int); typedef auto fntype(int) -> int; struct X { typedef void fn(int); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="struct" ) ), typedefs=[ Typedef( type=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ) ) ], ), name="fn", access="public", ) ], ) ], typedefs=[ Typedef( type=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], ), name="fn", ), Typedef( type=FunctionType( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], has_trailing_return=True, ), name="fntype", ), ], ) ) cxxheaderparser-1.3.1/tests/test_typefmt.py000066400000000000000000000241201455035044000211460ustar00rootroot00000000000000import typing import pytest from cxxheaderparser.tokfmt import Token from cxxheaderparser.types import ( Array, DecoratedType, FunctionType, FundamentalSpecifier, Method, MoveReference, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateSpecialization, TemplateDecl, Type, Value, ) @pytest.mark.parametrize( "pytype,typestr,declstr", [ ( Type(typename=PQName(segments=[FundamentalSpecifier(name="int")])), "int", "int name", ), ( Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]), const=True ), "const int", "const int name", ), ( Type( typename=PQName(segments=[NameSpecifier(name="S")], classkey="struct") ), "struct S", "struct S name", ), ( Pointer( ptr_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ), "int*", "int* name", ), ( Pointer( ptr_to=Pointer( ptr_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ) ), "int**", "int** name", ), ( Reference( ref_to=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ), "int&", "int& name", ), ( Reference( ref_to=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ) ), "int (&)[3]", "int (& name)[3]", ), ( MoveReference( moveref_to=Type( typename=PQName( segments=[NameSpecifier(name="T"), NameSpecifier(name="T")] ) ) ), "T::T&&", "T::T&& name", ), ( Pointer( ptr_to=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ) ), "int (*)[3]", "int (* name)[3]", ), ( Pointer( ptr_to=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ), const=True, ), "int (* const)[3]", "int (* const name)[3]", ), ( FunctionType( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), parameters=[ Parameter( type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ) ], ), "int (int)", "int name(int)", ), ( FunctionType( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), parameters=[ Parameter( type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ) ) ], has_trailing_return=True, ), "auto (int) -> int", "auto name(int) -> int", ), ( FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")], ), ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")], ), ), name="a", ), Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")], ), ), name="b", ), ], ), "void (int a, int b)", "void name(int a, int b)", ), ( Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], ) ), "int (*)(int)", "int (* name)(int)", ), ( Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ) ) ], ) ) ] ), ), ] ) ), "std::function", "std::function name", ), ( Type( typename=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="T"), ], ) ), ) ] ), ) ] ), ), "foo<::T>", "foo<::T> name", ), ( Type( typename=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="T"), ], has_typename=True, ) ), ) ] ), ) ] ), ), "foo", "foo name", ), ], ) def test_typefmt( pytype: typing.Union[DecoratedType, FunctionType], typestr: str, declstr: str ): # basic formatting assert pytype.format() == typestr # as a type declaration assert pytype.format_decl("name") == declstr cxxheaderparser-1.3.1/tests/test_union.py000066400000000000000000000126421455035044000206140ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( AnonymousName, ClassDecl, Field, FundamentalSpecifier, NameSpecifier, PQName, Type, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, parse_string, ParsedData, ) def test_union_basic() -> None: content = """ struct HAL_Value { union { int v_int; HAL_Bool v_boolean; } data; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="HAL_Value")], classkey="struct", ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="union" ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="v_int", ), Field( access="public", type=Type( typename=PQName( segments=[NameSpecifier(name="HAL_Bool")] ) ), name="v_boolean", ), ], ) ], fields=[ Field( access="public", type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="union" ) ), name="data", ) ], ) ] ) ) def test_union_anon_in_struct() -> None: content = """ struct Outer { union { int x; int y; }; int z; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="Outer")], classkey="struct" ) ), classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[AnonymousName(id=1)], classkey="union" ), access="public", ), fields=[ Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="x", ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="y", ), ], ) ], fields=[ Field( access="public", type=Type( typename=PQName( segments=[AnonymousName(id=1)], classkey="union" ) ), ), Field( access="public", type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="z", ), ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_using.py000066400000000000000000000702101455035044000206040ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.types import ( BaseClass, ClassDecl, Function, FunctionType, FundamentalSpecifier, Method, NameSpecifier, PQName, Parameter, Pointer, Reference, TemplateArgument, TemplateDecl, TemplateSpecialization, TemplateTypeParam, Token, Type, UsingAlias, UsingDecl, ) from cxxheaderparser.simple import ( ClassScope, NamespaceScope, UsingNamespace, parse_string, ParsedData, ) def test_using_namespace() -> None: content = """ using namespace foo; using namespace foo::bar; using namespace ::foo; using namespace ::foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_ns=[ UsingNamespace(ns="foo"), UsingNamespace(ns="foo::bar"), UsingNamespace(ns="::foo"), UsingNamespace(ns="::foo::bar"), ] ) ) def test_using_declaration() -> None: content = """ using ::foo; using foo::bar; using ::foo::bar; using typename ::foo::bar; using typename foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using=[ UsingDecl( typename=PQName( segments=[NameSpecifier(name=""), NameSpecifier(name="foo")] ) ), UsingDecl( typename=PQName( segments=[NameSpecifier(name="foo"), NameSpecifier(name="bar")] ) ), UsingDecl( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="foo"), NameSpecifier(name="bar"), ] ) ), UsingDecl( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="foo"), NameSpecifier(name="bar"), ] ) ), UsingDecl( typename=PQName( segments=[NameSpecifier(name="foo"), NameSpecifier(name="bar")] ) ), ] ) ) # alias-declaration def test_alias_declaration_1() -> None: content = """ using alias = foo; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type(typename=PQName(segments=[NameSpecifier(name="foo")])), ) ] ) ) def test_alias_declaration_2() -> None: content = """ template using alias = foo; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="T") ] ) ) ) ] ), ) ] ) ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ] ) ) def test_alias_declaration_3() -> None: content = """ using alias = ::foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="foo"), NameSpecifier(name="bar"), ] ) ), ) ] ) ) def test_alias_declaration_4() -> None: content = """ template using alias = ::foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName( segments=[ NameSpecifier(name=""), NameSpecifier(name="foo"), NameSpecifier( name="bar", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="T") ] ) ) ) ] ), ), ] ) ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ] ) ) def test_alias_declaration_5() -> None: content = """ using alias = foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName( segments=[ NameSpecifier(name="foo"), NameSpecifier(name="bar"), ] ) ), ) ] ) ) def test_alias_declaration_6() -> None: content = """ template using alias = foo::bar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using_alias=[ UsingAlias( alias="alias", type=Type( typename=PQName( segments=[ NameSpecifier( name="foo", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier(name="T") ] ) ) ) ] ), ), NameSpecifier(name="bar"), ] ) ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), ) ] ) ) def test_using_many_things() -> None: content = """ // clang-format off using std::thing; using MyThing = SomeThing; namespace a { using std::string; using VoidFunction = std::function; void fn(string &s, VoidFunction fn, thing * t); class A : public B { public: using B::B; using IntFunction = std::function; void a(string &s, IntFunction fn, thing * t); }; } """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( using=[ UsingDecl( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="thing"), ] ) ) ], using_alias=[ UsingAlias( alias="MyThing", type=Type( typename=PQName(segments=[NameSpecifier(name="SomeThing")]) ), ) ], namespaces={ "a": NamespaceScope( name="a", classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="A")], classkey="class" ), bases=[ BaseClass( access="public", typename=PQName( segments=[NameSpecifier(name="B")] ), ) ], ), methods=[ Method( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="a")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[ NameSpecifier(name="string") ] ) ) ), name="s", ), Parameter( type=Type( typename=PQName( segments=[ NameSpecifier( name="IntFunction" ) ] ) ), name="fn", ), Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[ NameSpecifier(name="thing") ] ) ) ), name="t", ), ], access="public", ) ], using=[ UsingDecl( typename=PQName( segments=[ NameSpecifier(name="B"), NameSpecifier(name="B"), ] ), access="public", ) ], using_alias=[ UsingAlias( alias="IntFunction", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="int" ) ] ) ), parameters=[], ) ) ] ), ), ] ) ), access="public", ) ], ) ], functions=[ Function( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), name=PQName(segments=[NameSpecifier(name="fn")]), parameters=[ Parameter( type=Reference( ref_to=Type( typename=PQName( segments=[NameSpecifier(name="string")] ) ) ), name="s", ), Parameter( type=Type( typename=PQName( segments=[ NameSpecifier(name="VoidFunction") ] ) ), name="fn", ), Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[NameSpecifier(name="thing")] ) ) ), name="t", ), ], ) ], using=[ UsingDecl( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier(name="string"), ] ) ) ], using_alias=[ UsingAlias( alias="VoidFunction", type=Type( typename=PQName( segments=[ NameSpecifier(name="std"), NameSpecifier( name="function", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=FunctionType( return_type=Type( typename=PQName( segments=[ FundamentalSpecifier( name="void" ) ] ) ), parameters=[], ) ) ] ), ), ] ) ), ) ], ) }, ) ) def test_using_template_in_class() -> None: content = """ class X { template using TT = U; }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="X")], classkey="class" ) ), using_alias=[ UsingAlias( alias="TT", type=Type( typename=PQName( segments=[ NameSpecifier( name="U", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="T" ) ] ) ) ) ] ), ) ] ) ), template=TemplateDecl( params=[TemplateTypeParam(typekey="typename", name="T")] ), access="private", ) ], ) ] ) ) def test_using_typename_in_class() -> None: content = """ template class P { using A = typename f::TP::A; public: using State = typename f::TP::S; P(State st); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="P")], classkey="class" ), template=TemplateDecl( params=[TemplateTypeParam(typekey="class", name="D")] ), ), methods=[ Method( return_type=None, name=PQName(segments=[NameSpecifier(name="P")]), parameters=[ Parameter( type=Type( typename=PQName( segments=[NameSpecifier(name="State")] ) ), name="st", ) ], access="public", constructor=True, ) ], using_alias=[ UsingAlias( alias="A", type=Type( typename=PQName( segments=[ NameSpecifier(name="f"), NameSpecifier( name="TP", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="D" ) ] ) ) ) ] ), ), NameSpecifier(name="A"), ], has_typename=True, ) ), access="private", ), UsingAlias( alias="State", type=Type( typename=PQName( segments=[ NameSpecifier(name="f"), NameSpecifier( name="TP", specialization=TemplateSpecialization( args=[ TemplateArgument( arg=Type( typename=PQName( segments=[ NameSpecifier( name="D" ) ] ) ) ) ] ), ), NameSpecifier(name="S"), ], has_typename=True, ) ), access="public", ), ], ) ] ) ) cxxheaderparser-1.3.1/tests/test_var.py000066400000000000000000000657541455035044000202700ustar00rootroot00000000000000# Note: testcases generated via `python -m cxxheaderparser.gentest` from cxxheaderparser.errors import CxxParseError from cxxheaderparser.types import ( Array, ClassDecl, EnumDecl, Enumerator, Field, FunctionType, FundamentalSpecifier, NameSpecifier, PQName, Parameter, Pointer, Reference, Token, Type, Value, Variable, ) from cxxheaderparser.simple import ClassScope, NamespaceScope, ParsedData, parse_string import pytest import re def test_var_unixwiz_ridiculous() -> None: # http://unixwiz.net/techtips/reading-cdecl.html # # .. "we have no idea how this variable is useful, but at least we can # describe the type correctly" content = """ char *(*(**foo[][8])())[]; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="foo")]), type=Array( array_of=Array( array_of=Pointer( ptr_to=Pointer( ptr_to=FunctionType( return_type=Pointer( ptr_to=Array( array_of=Pointer( ptr_to=Type( typename=PQName( segments=[ FundamentalSpecifier( name="char" ) ] ) ) ), size=None, ) ), parameters=[], ) ) ), size=Value(tokens=[Token(value="8")]), ), size=None, ), ) ] ) ) def test_var_ptr_to_array15_of_ptr_to_int() -> None: content = """ int *(*crocodile)[15]; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="crocodile")]), type=Pointer( ptr_to=Array( array_of=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), size=Value(tokens=[Token(value="15")]), ) ), ) ] ) ) def test_var_ref_to_array() -> None: content = """ int abase[3]; int (&aname)[3] = abase; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="abase")]), type=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ), ), Variable( name=PQName(segments=[NameSpecifier(name="aname")]), type=Reference( ref_to=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="3")]), ) ), value=Value(tokens=[Token(value="abase")]), ), ] ) ) def test_var_ptr_to_array() -> None: content = """ int zz, (*aname)[3] = &abase; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="zz")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ), Variable( name=PQName(segments=[NameSpecifier(name="aname")]), type=Pointer( ptr_to=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="3")]), ) ), value=Value(tokens=[Token(value="&"), Token(value="abase")]), ), ] ) ) def test_var_multi_1() -> None: content = """ int zz, (&aname)[3] = abase; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="zz")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ), Variable( name=PQName(segments=[NameSpecifier(name="aname")]), type=Reference( ref_to=Array( array_of=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), size=Value(tokens=[Token(value="3")]), ) ), value=Value(tokens=[Token(value="abase")]), ), ] ) ) def test_var_array_of_fnptr_varargs() -> None: content = """ void (*a3[3])(int, ...); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="a3")]), type=Array( array_of=Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ) ) ], vararg=True, ) ), size=Value(tokens=[Token(value="3")]), ), ) ] ) ) def test_var_double_fnptr_varargs() -> None: content = """ void (*(*a4))(int, ...); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="a4")]), type=Pointer( ptr_to=Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ) ) ], vararg=True, ) ) ), ) ] ) ) def test_var_fnptr_voidstar() -> None: content = """ void(*(*a5)(int)); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="a5")]), type=Pointer( ptr_to=FunctionType( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ) ], ) ), ) ] ) ) def test_var_fnptr_moreparens() -> None: content = """ void (*x)(int(p1), int); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ), name="p1", ), Parameter( type=Type( typename=PQName( segments=[FundamentalSpecifier(name="int")] ) ) ), ], ) ), ) ] ) ) # From pycparser: # Pointer decls nest from inside out. This is important when different # levels have different qualifiers. For example: # # char * const * p; # # Means "pointer to const pointer to char" # # While: # # char ** const p; # # Means "const pointer to pointer to char" def test_var_ptr_to_const_ptr_to_char() -> None: content = """ char *const *p; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="p")]), type=Pointer( ptr_to=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ), const=True, ) ), ) ] ) ) def test_var_const_ptr_to_ptr_to_char() -> None: content = """ char **const p; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="p")]), type=Pointer( ptr_to=Pointer( ptr_to=Type( typename=PQName( segments=[FundamentalSpecifier(name="char")] ) ) ), const=True, ), ) ] ) ) def test_var_array_initializer1() -> None: content = """ int x[3]{1, 2, 3}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ), value=Value( tokens=[ Token(value="{"), Token(value="1"), Token(value=","), Token(value="2"), Token(value=","), Token(value="3"), Token(value="}"), ] ), ) ] ) ) def test_var_array_initializer2() -> None: content = """ int x[3] = {1, 2, 3}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Array( array_of=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), size=Value(tokens=[Token(value="3")]), ), value=Value( tokens=[ Token(value="{"), Token(value="1"), Token(value=","), Token(value="2"), Token(value=","), Token(value="3"), Token(value="}"), ] ), ) ] ) ) def test_var_extern_c() -> None: content = """ extern "C" int x; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), # TODO: store linkage extern=True, ) ] ) ) def test_var_ns_1() -> None: content = """ int N::x; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName( segments=[NameSpecifier(name="N"), NameSpecifier(name="x")] ), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), ) ] ) ) def test_var_ns_2() -> None: content = """ int N::x = 4; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName( segments=[NameSpecifier(name="N"), NameSpecifier(name="x")] ), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value(tokens=[Token(value="4")]), ) ] ) ) def test_var_ns_3() -> None: content = """ int N::x{4}; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName( segments=[NameSpecifier(name="N"), NameSpecifier(name="x")] ), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value( tokens=[Token(value="{"), Token(value="4"), Token(value="}")] ), ) ] ) ) def test_var_static_struct() -> None: content = """ constexpr static struct SS {} s; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="SS")], classkey="struct" ) ) ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="s")]), type=Type( typename=PQName( segments=[NameSpecifier(name="SS")], classkey="struct" ) ), constexpr=True, static=True, ) ], ) ) def test_var_constexpr_enum() -> None: content = """ constexpr enum E { EE } e = EE; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( enums=[ EnumDecl( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ), values=[Enumerator(name="EE")], ) ], variables=[ Variable( name=PQName(segments=[NameSpecifier(name="e")]), type=Type( typename=PQName( segments=[NameSpecifier(name="E")], classkey="enum" ) ), value=Value(tokens=[Token(value="EE")]), constexpr=True, ) ], ) ) def test_var_fnptr_in_class() -> None: content = """ struct DriverFuncs { void *(*init)(); void (*write)(void *buf, int buflen); }; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( classes=[ ClassScope( class_decl=ClassDecl( typename=PQName( segments=[NameSpecifier(name="DriverFuncs")], classkey="struct", ) ), fields=[ Field( access="public", type=Pointer( ptr_to=FunctionType( return_type=Pointer( ptr_to=Type( typename=PQName( segments=[ FundamentalSpecifier(name="void") ] ) ) ), parameters=[], ) ), name="init", ), Field( access="public", type=Pointer( ptr_to=FunctionType( return_type=Type( typename=PQName( segments=[FundamentalSpecifier(name="void")] ) ), parameters=[ Parameter( type=Pointer( ptr_to=Type( typename=PQName( segments=[ FundamentalSpecifier( name="void" ) ] ) ) ), name="buf", ), Parameter( type=Type( typename=PQName( segments=[ FundamentalSpecifier(name="int") ] ) ), name="buflen", ), ], ) ), name="write", ), ], ) ] ) ) def test_var_extern() -> None: content = """ extern int externVar; """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="externVar")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), extern=True, ) ] ) ) def test_balanced_with_gt() -> None: """Tests _consume_balanced_tokens handling of mismatched gt tokens""" content = """ int x = (1 >> 2); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="x")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="int")]) ), value=Value( tokens=[ Token(value="("), Token(value="1"), Token(value=">"), Token(value=">"), Token(value="2"), Token(value=")"), ] ), ) ] ) ) def test_balanced_with_lt() -> None: """Tests _consume_balanced_tokens handling of mismatched lt tokens""" content = """ bool z = (i < 4); """ data = parse_string(content, cleandoc=True) assert data == ParsedData( namespace=NamespaceScope( variables=[ Variable( name=PQName(segments=[NameSpecifier(name="z")]), type=Type( typename=PQName(segments=[FundamentalSpecifier(name="bool")]) ), value=Value( tokens=[ Token(value="("), Token(value="i"), Token(value="<"), Token(value="4"), Token(value=")"), ] ), ) ] ) ) def test_balanced_bad_mismatch() -> None: content = """ bool z = (12 ]); """ err = ":1: parse error evaluating ']': unexpected ']', expected ')'" with pytest.raises(CxxParseError, match=re.escape(err)): parse_string(content, cleandoc=True)