Initial commit

This commit is contained in:
Dustin Spicuzza 2020-12-28 03:35:30 -05:00
commit ef5c22972b
37 changed files with 14826 additions and 0 deletions

94
.github/workflows/dist.yml vendored Normal file
View File

@ -0,0 +1,94 @@
---
name: dist
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: psf/black@stable
# check-doc:
# runs-on: ubuntu-18.04
# steps:
# - uses: actions/checkout@v2
# with:
# submodules: recursive
# fetch-depth: 0
# - uses: actions/setup-python@v2
# 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"
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-18.04]
python_version: [3.6, 3.7, 3.8, 3.9]
architecture: [x86, x64]
exclude:
- os: macos-latest
architecture: x86
- os: ubuntu-18.04
architecture: x86
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python_version }}
architecture: ${{ matrix.architecture }}
- name: Install build dependencies
run: python -m pip --disable-pip-version-check install wheel
- name: Build wheel
run: python setup.py bdist_wheel
- name: Install test dependencies
run: python -m pip --disable-pip-version-check install pytest
- 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-doc, test]
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
steps:
- uses: actions/checkout@v2
with:
submodules: recursive
fetch-depth: 0
- uses: actions/setup-python@v2
with:
python-version: 3.8
- run: pip --disable-pip-version-check install wheel
- name: Build packages
run: python setup.py sdist bdist_wheel
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
with:
user: __token__
password: ${{ secrets.PYPI_PASSWORD }}

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
*.py[cod]
*.egg-info
/build
/dist
/cxxheaderparser/version.py
/.vscode
.coverage
.pytest_cache

104
LICENSE.txt Normal file
View File

@ -0,0 +1,104 @@
cxxheaderparser license:
Copyright (c) 2020 Dustin Spicuzza <dustin@virtualroadside.com>
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.
-----------------------------------------------------------------------------

108
README.md Normal file
View File

@ -0,0 +1,108 @@
cxxheaderparser
===============
**Note**: This is still a work in progress, but should be stable in a few weeks
once I port robotpy-build over to use it
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
* 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
Documentation
-------------
TODO: documentation site
Install
-------
Requires Python 3.6+, no non-stdlib dependencies if using Python 3.7+.
TODO: distribute on pip
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 dont 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

View File

@ -0,0 +1,4 @@
try:
from .version import __version__
except ImportError:
__version__ = "master"

View File

@ -0,0 +1,4 @@
from cxxheaderparser.dump import dumpmain
if __name__ == "__main__":
dumpmain()

View File

902
cxxheaderparser/_ply/lex.py Normal file
View File

@ -0,0 +1,902 @@
# fmt: off
# -----------------------------------------------------------------------------
# ply: lex.py
#
# 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.
# -----------------------------------------------------------------------------
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

53
cxxheaderparser/dump.py Normal file
View File

@ -0,0 +1,53 @@
import argparse
import dataclasses
import json
import pprint
import subprocess
import sys
from .options import ParserOptions
from .simple import parse_file
def dumpmain():
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"], default="pprint"
)
args = parser.parse_args()
options = ParserOptions(verbose=args.verbose)
data = parse_file(args.header, 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")

12
cxxheaderparser/errors.py Normal file
View File

@ -0,0 +1,12 @@
import typing
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

View File

@ -0,0 +1,98 @@
import argparse
import dataclasses
import inspect
import subprocess
from .options import ParserOptions
from .simple import parse_string
def nondefault_repr(data):
"""
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) -> 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):
# 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()
options = ParserOptions(verbose=verbose)
data = parse_string(content, options=options)
stmt = nondefault_repr(data)
content = content.replace("\n", "\n ")
stmt = inspect.cleandoc(
f'''
def test_{name}():
content = """
{content}
"""
data = parse_string(content, cleandoc=True)
assert data == {stmt}
'''
)
# 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("-v", "--verbose", default=False, action="store_true")
parser.add_argument("-o", "--output", default="-")
args = parser.parse_args()
gentest(args.header, args.name, args.output, args.verbose)

425
cxxheaderparser/lexer.py Normal file
View File

@ -0,0 +1,425 @@
from collections import deque
import re
import typing
import sys
from ._ply import lex
if sys.version_info >= (3, 8):
Protocol = typing.Protocol
else:
Protocol = object
_line_re = re.compile(r'^#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
class Lexer:
keywords = {
"__attribute__",
"alignas",
"alignof",
"asm",
"auto",
"bool",
"break",
"case",
"catch",
"char",
"char8_t",
"char16_t",
"char32_t",
"class",
"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",
"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 = [
"NUMBER",
"FLOAT_NUMBER",
"NAME",
"COMMENT_SINGLELINE",
"COMMENT_MULTILINE",
"PRECOMP_MACRO",
"DIVIDE",
"CHAR_LITERAL",
"STRING_LITERAL",
"NEWLINE",
"ELLIPSIS",
"DBL_LBRACKET",
"DBL_RBRACKET",
"DBL_COLON",
"DBL_AMP",
"SHIFT_LEFT",
] + list(keywords)
literals = [
"<",
">",
"(",
")",
"{",
"}",
"[",
"]",
";",
":",
",",
"\\",
"|",
"%",
"^",
"!",
"*",
"-",
"+",
"&",
"=",
"'",
".",
]
t_ignore = " \t\r?@\f"
t_NUMBER = r"[0-9][0-9XxA-Fa-f]*"
t_FLOAT_NUMBER = r"[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?"
def t_NAME(self, t):
r"[A-Za-z_~][A-Za-z0-9_]*"
if t.value in self.keywords:
t.type = t.value
return t
def t_PRECOMP_MACRO(self, t):
r"\#.*"
m = _line_re.match(t.value)
if m:
filename = m.group(2)
if filename not in self._filenames_set:
self.filenames.append(filename)
self._filenames_set.add(filename)
self.filename = filename
self.line_offset = 1 + self.lex.lineno - int(m.group(1))
else:
return t
def t_COMMENT_SINGLELINE(self, t):
r"\/\/.*\n?"
if t.value.startswith("///") or t.value.startswith("//!"):
self.comments.append(t.value.lstrip("\t ").rstrip("\n"))
t.lexer.lineno += t.value.count("\n")
return t
t_DIVIDE = r"/(?!/)"
t_CHAR_LITERAL = "'.'"
t_ELLIPSIS = r"\.\.\."
t_DBL_LBRACKET = r"\[\["
t_DBL_RBRACKET = r"\]\]"
t_DBL_COLON = r"::"
t_DBL_AMP = r"&&"
t_SHIFT_LEFT = r"<<"
# SHIFT_RIGHT introduces ambiguity
# found at http://wordaligned.org/articles/string-literals-and-regular-expressions
# TODO: This does not work with the string "bla \" bla"
t_STRING_LITERAL = r'"([^"\\]|\\.)*"'
# Found at http://ostermiller.org/findcomment.html
def t_COMMENT_MULTILINE(self, t):
r"/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/\n?"
if t.value.startswith("/**") or t.value.startswith("/*!"):
# not sure why, but get double new lines
v = t.value.replace("\n\n", "\n")
# strip prefixing whitespace
v = _multicomment_re.sub("\n*", v)
self.comments = v.splitlines()
t.lexer.lineno += t.value.count("\n")
return t
def t_NEWLINE(self, t):
r"\n+"
t.lexer.lineno += len(t.value)
del self.comments[:]
return t
def t_error(self, v):
print("Lex error: ", v)
_lexer = None
def __new__(cls, *args, **kwargs):
# 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)
return inst
def __init__(self, filename: typing.Optional[str] = None):
self.input = self.lex.input
# For tracking current file/line position
self.filename = filename
self.line_offset = 0
self.filenames = []
self._filenames_set = set()
if self.filename:
self.filenames.append(filename)
self._filenames_set.add(filename)
# Doxygen comments
self.comments = []
self.lookahead = typing.Deque[LexToken]()
def current_location(self) -> Location:
if self.lookahead:
return self.lookahead[0].location
return Location(self.filename, self.lex.lineno - self.line_offset)
def get_doxygen(self) -> typing.Optional[str]:
"""
This should be called after the first element of something has
been consumed.
It will lookahead for comments that come after the item, if prior
comments don't exist.
"""
# Assumption: This function is either called at the beginning of a
# statement or at the end of a statement
if self.comments:
comments = self.comments
else:
comments = []
# only look for comments until a newline (including lookahead)
for tok in self.lookahead:
if tok.type == "NEWLINE":
return None
while True:
tok = self.lex.token()
comments.extend(self.comments)
if tok is None:
break
tok.location = Location(self.filename, tok.lineno - self.line_offset)
ttype = tok.type
if ttype == "NEWLINE":
self.lookahead.append(tok)
break
if ttype not in self._discard_types:
self.lookahead.append(tok)
if ttype == "NAME":
break
del self.comments[:]
comments = "\n".join(comments)
del self.comments[:]
if comments:
return comments
return None
_discard_types = {"NEWLINE", "COMMENT_SINGLELINE", "COMMENT_MULTILINE"}
def token(self) -> LexToken:
tok = None
while self.lookahead:
tok = self.lookahead.popleft()
if tok.type not in self._discard_types:
return tok
while True:
tok = self.lex.token()
if tok is None:
raise EOFError("unexpected end of file")
if tok.type not in self._discard_types:
tok.location = Location(self.filename, tok.lineno - self.line_offset)
break
return tok
def token_eof_ok(self) -> typing.Optional[LexToken]:
tok = None
while self.lookahead:
tok = self.lookahead.popleft()
if tok.type not in self._discard_types:
return tok
while True:
tok = self.lex.token()
if tok is None:
break
if tok.type not in self._discard_types:
tok.location = Location(self.filename, tok.lineno - self.line_offset)
break
return tok
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:
# put it back on the left in case it was retrieved
# from the lookahead buffer
self.lookahead.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:
# put it back on the left in case it was retrieved
# from the lookahead buffer
self.lookahead.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:
# put it back on the left in case it was retrieved
# from the lookahead buffer
self.lookahead.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:
# put it back on the left in case it was retrieved
# from the lookahead buffer
self.lookahead.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.lookahead.appendleft(tok)
return tok.type in types
def return_token(self, tok: LexToken) -> None:
self.lookahead.appendleft(tok)
def return_tokens(self, toks: typing.Iterable[LexToken]) -> None:
self.lookahead.extendleft(reversed(toks))
if __name__ == "__main__":
try:
lex.runmain(lexer=Lexer(None))
except EOFError:
pass

View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
@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

2082
cxxheaderparser/parser.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,114 @@
import typing
if typing.TYPE_CHECKING:
from .visitor import CxxVisitor
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}'")
class State:
#: parent state
parent: typing.Optional["State"]
def __init__(self, parent: typing.Optional["State"]) -> None:
self.parent = parent
def _finish(self, visitor: "CxxVisitor") -> None:
pass
class BlockState(State):
#: Approximate location that the parsed element was found at
location: Location
class EmptyBlockState(BlockState):
def _finish(self, visitor: "CxxVisitor") -> None:
visitor.on_empty_block_end(self)
class ExternBlockState(BlockState):
#: The linkage for this extern block
linkage: str
def __init__(self, parent: typing.Optional[State], linkage: str) -> None:
super().__init__(parent)
self.linkage = linkage
def _finish(self, visitor: "CxxVisitor"):
visitor.on_extern_block_end(self)
class NamespaceBlockState(BlockState):
#: The incremental namespace for this block
namespace: NamespaceDecl
def __init__(
self, parent: typing.Optional[State], namespace: NamespaceDecl
) -> None:
super().__init__(parent)
self.namespace = namespace
def _finish(self, visitor: "CxxVisitor") -> None:
visitor.on_namespace_end(self)
class ClassBlockState(BlockState):
#: 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],
class_decl: ClassDecl,
access: str,
typedef: bool,
mods: ParsedTypeModifiers,
) -> None:
super().__init__(parent)
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)

294
cxxheaderparser/simple.py Normal file
View File

@ -0,0 +1,294 @@
"""
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.
"""
import inspect
import typing
from dataclasses import dataclass, field
from .types import (
ClassDecl,
EnumDecl,
Field,
ForwardDecl,
FriendDecl,
Function,
Method,
Typedef,
UsingAlias,
UsingDecl,
Variable,
)
from .parserstate import (
State,
EmptyBlockState,
ClassBlockState,
ExternBlockState,
NamespaceBlockState,
)
from .parser import CxxParser
from .options import ParserOptions
#
# Data structure
#
@dataclass
class ClassScope:
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:
name: str = ""
classes: typing.List["ClassScope"] = field(default_factory=list)
enums: typing.List[EnumDecl] = field(default_factory=list)
functions: 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[UsingDecl] = field(default_factory=list)
using_alias: typing.List[UsingAlias] = field(default_factory=list)
#: Child namespaces
namespaces: typing.Dict[str, "NamespaceScope"] = field(default_factory=dict)
Block = typing.Union[ClassScope, NamespaceScope]
@dataclass
class Define:
content: str
@dataclass
class Pragma:
content: str
@dataclass
class Include:
#: The filename includes the surrounding ``<>`` or ``"``
filename: str
@dataclass
class UsingNamespace:
ns: str
@dataclass
class ParsedData:
namespace: NamespaceScope = field(default_factory=lambda: NamespaceScope())
defines: typing.List[Define] = field(default_factory=list)
pragmas: typing.List[Pragma] = field(default_factory=list)
includes: typing.List[Include] = field(default_factory=list)
#
# Visitor implementation
#
class SimpleCxxVisitor:
"""
A simple visitor that stores all of the C++ elements passed to it
in an "easy" to use data structure
.. 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.
"""
data: ParsedData
namespace: NamespaceScope
block: Block
def __init__(self):
self.namespace = NamespaceScope("")
self.block = self.namespace
self.ns_stack = typing.Deque[NamespaceScope]()
self.block_stack = typing.Deque[Block]()
self.data = ParsedData(self.namespace)
def on_define(self, state: State, content: str) -> None:
self.data.defines.append(Define(content))
def on_pragma(self, state: State, content: str) -> None:
self.data.pragmas.append(Pragma(content))
def on_include(self, state: State, filename: str) -> None:
self.data.includes.append(Include(filename))
def on_empty_block_start(self, state: EmptyBlockState) -> None:
# this matters for some scope/resolving purposes, but you're
# probably going to want to use clang if you care about that
# level of detail
pass
def on_empty_block_end(self, state: EmptyBlockState) -> None:
pass
def on_extern_block_start(self, state: ExternBlockState) -> None:
pass # TODO
def on_extern_block_end(self, state: ExternBlockState) -> None:
pass
def on_namespace_start(self, state: NamespaceBlockState) -> None:
parent_ns = self.namespace
self.block_stack.append(parent_ns)
self.ns_stack.append(parent_ns)
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
self.block = ns
self.namespace = ns
def on_namespace_end(self, state: NamespaceBlockState) -> None:
self.block = self.block_stack.pop()
self.namespace = self.ns_stack.pop()
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
self.block.forward_decls.append(fdecl)
def on_variable(self, state: State, v: Variable) -> None:
self.block.variables.append(v)
def on_function(self, state: State, fn: Function) -> None:
self.block.functions.append(fn)
def on_typedef(self, state: State, typedef: Typedef) -> None:
self.block.typedefs.append(typedef)
def on_using_namespace(self, state: State, namespace: typing.List[str]) -> None:
ns = UsingNamespace("::".join(namespace))
self.block.using_ns.append(ns)
def on_using_alias(self, state: State, using: UsingAlias):
self.block.using_alias.append(using)
def on_using_declaration(self, state: State, using: UsingDecl) -> None:
self.block.using.append(using)
#
# Enums
#
def on_enum(self, state: State, enum: EnumDecl) -> None:
self.block.enums.append(enum)
#
# Class/union/struct
#
def on_class_start(self, state: ClassBlockState) -> None:
block = ClassScope(state.class_decl)
self.block.classes.append(block)
self.block_stack.append(self.block)
self.block = block
def on_class_field(self, state: State, f: Field) -> None:
self.block.fields.append(f)
def on_class_method(self, state: ClassBlockState, method: Method) -> None:
self.block.methods.append(method)
def on_class_friend(self, state: ClassBlockState, friend: FriendDecl):
self.block.friends.append(friend)
def on_class_end(self, state: ClassBlockState) -> None:
self.block = self.block_stack.pop()
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: str,
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
"""
with open(filename, encoding=encoding) as fp:
content = fp.read()
return parse_string(content, filename=filename, options=options)

74
cxxheaderparser/tokfmt.py Normal file
View File

@ -0,0 +1,74 @@
import typing
from .lexer import Lexer
from .types import Token
# key: token type, value: (left spacing, right spacing)
_want_spacing = {
"NUMBER": (2, 2),
"FLOAT_NUMBER": (2, 2),
"NAME": (2, 2),
"CHAR_LITERAL": (2, 2),
"STRING_LITERAL": (2, 2),
"ELLIPSIS": (2, 2),
">": (0, 2),
")": (0, 1),
"(": (1, 0),
",": (0, 3),
"*": (1, 2),
"&": (0, 2),
}
_want_spacing.update(dict.fromkeys(Lexer.keywords, (2, 2)))
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__":
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("header")
args = parser.parse_args()
lexer = Lexer(args.header)
with open(lexer.filename) as fp:
lexer.input(fp.read())
toks = []
while True:
tok = lexer.token_eof_ok()
if not tok:
break
if tok.type == ";":
print(toks)
print(tokfmt(toks))
toks = []
else:
toks.append(tok)
print(toks)
print(tokfmt(toks))

650
cxxheaderparser/types.py Normal file
View File

@ -0,0 +1,650 @@
import typing
from dataclasses import dataclass, field
@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="")
@dataclass
class Value:
"""
A unparsed list of tokens
.. code-block:: c++
int x = 0x1337;
~~~~~~
"""
#: Tokens corresponding to the value
tokens: typing.List[Token]
@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
@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]
@dataclass
class FundamentalSpecifier:
"""
A specifier that only contains fundamental types
"""
name: str
@dataclass
class NameSpecifier:
"""
An individual segment of a type name
.. code-block:: c++
Foo::Bar
~~~
"""
name: str
specialization: typing.Optional[typing.List["TemplateSpecialization"]] = None
@dataclass
class AutoSpecifier:
"""
Used for an auto return type
"""
name: str = "auto"
@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
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
@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 TemplateArgument:
"""
A single argument for a template specialization
.. code-block:: c++
Foo<int, Bar...>
~~~
"""
#: This contains unparsed arbitrary expressions, including additional
#: specializations or decltypes or whatever
tokens: typing.List[Token]
@dataclass
class TemplateSpecialization:
"""
Contains the arguments of a template specialization
.. code-block:: c++s
Foo<int, Bar...>
~~~~~~~~~~~
"""
args: typing.List[TemplateArgument]
#: If True, indicates a parameter pack (...) on the last parameter
param_pack: bool = False
@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
@dataclass
class Type:
""""""
typename: PQName
const: bool = False
volatile: bool = False
def get_type(self) -> "Type":
return self
@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 get_type(self) -> Type:
return self.array_of.get_type()
@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 get_type(self) -> Type:
return self.ptr_to.get_type()
@dataclass
class Reference:
"""
A lvalue (``&``) reference
"""
ref_to: typing.Union[Array, Pointer, Type]
def get_type(self) -> Type:
return self.ref_to.get_type()
@dataclass
class MoveReference:
"""
An rvalue (``&&``) reference
"""
moveref_to: typing.Union[Array, Pointer, Type]
def get_type(self) -> Type:
return self.moveref_to.get_type()
#: 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 TemplateNonTypeParam:
"""
.. code-block:: c++
template <int T>
~~~~~
template <class T, typename T::type* U>
~~~~~~~~~~~~~~~~~~~
template <auto T>
~~~~~~
"""
type: DecoratedType
name: typing.Optional[str] = None
default: typing.Optional[Value] = None
#: Contains a ``...``
param_pack: bool = False
@dataclass
class TemplateTypeParam:
"""
.. code-block:: c++
template <typename T>
~~~~~~~~~~
"""
#: '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 <typename T>
#: ~~~~~~~~~~
TemplateParam = typing.Union[TemplateNonTypeParam, TemplateTypeParam]
@dataclass
class TemplateDecl:
"""
Template declaration for a function or class
.. code-block:: c++
template <typename T>
class Foo {};
template <typename T>
T fn();
"""
params: typing.List[TemplateParam] = field(default_factory=list)
@dataclass
class ForwardDecl:
"""
Represents a forward declaration of a user defined type
"""
typename: PQName
template: typing.Optional[TemplateDecl] = 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: typing.Optional[TemplateDecl] = 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) -> 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
@dataclass
class Function:
"""
A function declaration, potentially with the function body
"""
return_type: 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
template: typing.Optional[TemplateDecl] = None
throw: typing.Optional[Value] = None
noexcept: typing.Optional[Value] = None
@dataclass
class Method(Function):
"""
A method declaration, potentially with the method body
"""
#: constructors and destructors don't have a return type
return_type: typing.Optional[DecoratedType]
access: str = ""
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 Operator(Method):
operator: str = ""
@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
#:
#: .. code-block:: c++
#:
#: typedef type *pname;
#: ~~~~~~
type: DecoratedType
#: 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
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
@dataclass
class UsingAlias:
"""
.. code-block:: c++
using foo = int;
template <typename T>
using VectorT = std::vector<T>;
"""
alias: str
type: DecoratedType
template: typing.Optional[TemplateDecl] = None
#: If within a class, the access level for this decl
access: typing.Optional[str] = None

197
cxxheaderparser/visitor.py Normal file
View File

@ -0,0 +1,197 @@
import sys
import typing
if sys.version_info >= (3, 8):
Protocol = typing.Protocol
else:
Protocol = object
from .types import (
EnumDecl,
Field,
ForwardDecl,
FriendDecl,
Function,
Method,
Typedef,
UsingAlias,
UsingDecl,
Variable,
)
from .parserstate import (
State,
EmptyBlockState,
ClassBlockState,
ExternBlockState,
NamespaceBlockState,
)
class CxxVisitor(Protocol):
"""
Defines the interface used by the parser to emit events
"""
def on_define(self, state: State, content: str) -> None:
"""
.. warning:: cxxheaderparser intentionally does not have a C preprocessor
implementation. If you are parsing code with macros in it,
use a conforming preprocessor like ``pcpp``
"""
def on_pragma(self, state: State, content: str) -> 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_empty_block_start(self, state: EmptyBlockState) -> None:
"""
Called when a ``{`` is encountered that isn't associated with or
consumed by other declarations.
.. code-block:: c++
{
// stuff
}
"""
def on_empty_block_end(self, state: EmptyBlockState) -> None:
...
def on_extern_block_start(self, state: ExternBlockState) -> None:
"""
.. code-block:: c++
extern "C" {
}
"""
def on_extern_block_end(self, state: ExternBlockState) -> None:
...
def on_namespace_start(self, state: NamespaceBlockState) -> None:
"""
Called when a ``namespace`` directive is encountered
"""
def on_namespace_end(self, state: NamespaceBlockState) -> None:
"""
Called at the end of a ``namespace`` block
"""
def on_forward_decl(self, state: State, fdecl: ForwardDecl) -> None:
"""
Called when a forward declaration is encountered
"""
def on_variable(self, state: State, v: Variable) -> None:
...
def on_function(self, state: State, fn: Function) -> None:
...
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: State, namespace: typing.List[str]) -> None:
"""
.. code-block:: c++
using namespace std;
"""
def on_using_alias(self, state: State, using: UsingAlias):
"""
.. code-block:: c++
using foo = int;
template <typename T>
using VectorT = std::vector<T>;
"""
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) -> None:
"""
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.
"""
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):
"""
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
"""
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.
"""

77
setup.py Normal file
View File

@ -0,0 +1,77 @@
#!/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'"],
license="BSD",
platforms="Platform Independent",
packages=find_packages(),
keywords="c++ header parser ply",
python_requires=">= 3.6",
classifiers=CLASSIFIERS,
)

25
tests/README.md Normal file
View File

@ -0,0 +1,25 @@
Tests
=====
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)

148
tests/test_attributes.py Normal file
View File

@ -0,0 +1,148 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
ClassDecl,
EnumDecl,
Enumerator,
Field,
Function,
FundamentalSpecifier,
NameSpecifier,
PQName,
Pointer,
Token,
Type,
Typedef,
Value,
Variable,
)
from cxxheaderparser.simple import (
ClassScope,
NamespaceScope,
parse_string,
ParsedData,
)
def test_attributes_everywhere():
# 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():
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"),
],
)
]
)
)

2843
tests/test_class.py Normal file

File diff suppressed because it is too large Load Diff

247
tests/test_class_base.py Normal file
View File

@ -0,0 +1,247 @@
# 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():
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():
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():
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():
content = """
class Pea : public Vegetable<Green> {
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(
tokens=[Token(value="Green")]
)
]
),
)
]
),
)
],
),
fields=[
Field(
access="private",
type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="int")]
)
),
name="i",
)
],
)
]
)
)

221
tests/test_doxygen.py Normal file
View File

@ -0,0 +1,221 @@
# 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,
Operator,
PQName,
Parameter,
Pointer,
Reference,
TemplateArgument,
TemplateDecl,
TemplateSpecialization,
TemplateTypeParam,
Token,
Type,
Typedef,
UsingDecl,
Value,
Variable,
)
from cxxheaderparser.simple import (
ClassScope,
NamespaceScope,
parse_string,
ParsedData,
)
r"""
class SampleClass: public BaseSampleClass
{
public:
enum Elephant
{
EL_ONE = 1,
EL_TWO = 2,
EL_NINE = 9,
EL_TEN,
};
SampleClass();
/*!
* Method 1
*/
string meth1();
///
/// Method 2 description
///
/// @param v1 Variable 1
///
int meth2(int v1);
/**
* Method 3 description
*
* \param v1 Variable 1 with a really long
* wrapping description
* \param v2 Variable 2
*/
void meth3(const string & v1, vector<string> & v2);
/**********************************
* Method 4 description
*
* @return Return value
*********************************/
unsigned int meth4();
private:
void * meth5(){return NULL;}
/// prop1 description
string prop1;
//! prop5 description
int prop5;
bool prop6; /*!< prop6 description */
double prop7; //!< prop7 description
//!< with two lines
/// prop8 description
int prop8;
};
namespace Alpha
{
class AlphaClass
{
public:
AlphaClass();
void alphaMethod();
string alphaString;
protected:
typedef enum
{
Z_A,
Z_B = 0x2B,
Z_C = 'j',
Z_D,
} Zebra;
};
namespace Omega
{
class OmegaClass
{
public:
OmegaClass();
string omegaString;
protected:
///
/// @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;
};
};
}
"""
# def test_doxygen_messy():
# content = """
# // clang-format off
# /// fn comment
# void
# fn();
# /// var comment
# int
# v1 = 0;
# int
# v2 = 0; /// var2 comment
# /// cls comment
# class
# C {};
# /// template comment
# template <typename T>
# class
# C2 {};
# """
# 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",
# )
# ),
# ClassScope(
# class_decl=ClassDecl(
# typename=PQName(
# segments=[NameSpecifier(name="C2")], classkey="class"
# ),
# template=TemplateDecl(
# params=[TemplateTypeParam(typekey="typename", name="T")]
# ),
# doxygen="/// template comment",
# )
# ),
# ],
# functions=[
# Function(
# return_type=Type(
# typename=PQName(segments=[FundamentalSpecifier(name="void")])
# ),
# name=PQName(segments=[NameSpecifier(name="fn")]),
# parameters=[],
# doxygen="/// fn comment",
# )
# ],
# 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",
# ),
# Variable(
# name=PQName(segments=[NameSpecifier(name="v2")]),
# type=Type(
# typename=PQName(segments=[FundamentalSpecifier(name="int")])
# ),
# value=Value(tokens=[Token(value="0")]),
# ),
# ],
# )
# )

649
tests/test_enum.py Normal file
View File

@ -0,0 +1,649 @@
from 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():
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():
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():
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="\\"),
Token(value="'A'"),
Token(value="<<"),
Token(value="16"),
Token(value="|"),
Token(value="\\"),
Token(value="'S'"),
Token(value="<<"),
Token(value="8"),
Token(value="|"),
Token(value="\\"),
Token(value="'H'"),
Token(value=")"),
]
),
)
],
)
]
)
)
def test_basic_enum_class():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
content = """
enum {
IsRandomAccess = std::is_base_of<std::random_access_iterator_tag,
IteratorCategoryT>::value,
IsBidirectional = std::is_base_of<std::bidirectional_iterator_tag,
IteratorCategoryT>::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():
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,
),
],
)
)

506
tests/test_fn.py Normal file
View File

@ -0,0 +1,506 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
Array,
AutoSpecifier,
Function,
FundamentalSpecifier,
NameSpecifier,
PQName,
Parameter,
Pointer,
Reference,
TemplateArgument,
TemplateDecl,
TemplateSpecialization,
TemplateTypeParam,
Token,
Type,
)
from cxxheaderparser.simple import (
NamespaceScope,
parse_string,
ParsedData,
)
def test_fn_returns_class():
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_pointer_params():
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():
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():
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_weird_refs():
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():
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():
# 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():
content = """
template<class T, class U>
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():
content = """
std::vector<Pointer *> *fn(std::vector<Pointer *> *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(
tokens=[
Token(value="Pointer"),
Token(value="*"),
]
)
]
),
),
]
)
)
),
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(
tokens=[
Token(value="Pointer"),
Token(value="*"),
]
)
]
),
),
]
)
)
),
name="ps",
)
],
)
]
)
)
def test_fn_with_impl():
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,
)
]
)
)

382
tests/test_friends.py Normal file
View File

@ -0,0 +1,382 @@
# 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():
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():
content = """
template <typename T> struct X { static int x; };
struct BFF {
void fn() const;
};
struct F {
friend enum B;
friend void BFF::fn() const;
template <typename T> 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():
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():
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",
)
)
],
)
]
)
)

170
tests/test_misc.py Normal file
View File

@ -0,0 +1,170 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
Function,
FundamentalSpecifier,
NameSpecifier,
PQName,
Parameter,
Type,
Variable,
)
from cxxheaderparser.simple import (
Include,
NamespaceScope,
Pragma,
parse_string,
ParsedData,
Define,
)
#
# minimal preprocessor support
#
def test_define():
content = """
#define simple
#define complex(thing) stuff(thing)
# define spaced
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
defines=[
Define(content="simple"),
Define(content="complex(thing) stuff(thing)"),
Define(content="spaced"),
],
)
def test_includes():
content = """
#include <global.h>
#include "local.h"
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(includes=[Include("<global.h>"), Include('"local.h"')])
def test_pragma():
content = """
#pragma once
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(pragmas=[Pragma(content="once")])
#
# extern "C"
#
def test_extern_c():
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():
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():
# static_assert should be ignored
content = """
static_assert(x == 1);
"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData()
def test_static_assert_2():
# 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():
content = """
namespace a {} // namespace a"""
data = parse_string(content, cleandoc=True)
assert data == ParsedData(
namespace=NamespaceScope(namespaces={"a": NamespaceScope(name="a")})
)

121
tests/test_namespaces.py Normal file
View File

@ -0,0 +1,121 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
FundamentalSpecifier,
NameSpecifier,
PQName,
Token,
Type,
Value,
Variable,
)
from cxxheaderparser.simple import (
NamespaceScope,
parse_string,
ParsedData,
)
def test_dups_in_different_ns():
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():
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")
]
)
),
),
],
)
},
)
},
)
}
)
)

554
tests/test_operators.py Normal file
View File

@ -0,0 +1,554 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
ClassDecl,
FundamentalSpecifier,
NameSpecifier,
Operator,
PQName,
Parameter,
Reference,
Type,
)
from cxxheaderparser.simple import (
ClassScope,
NamespaceScope,
parse_string,
ParsedData,
)
def test_class_operators():
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=[
Operator(
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="=",
),
Operator(
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="-=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator+=")]),
parameters=[],
access="public",
operator="+=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator[]")]),
parameters=[],
access="public",
operator="[]",
),
Operator(
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="==",
),
Operator(
return_type=Reference(
ref_to=Type(
typename=PQName(
segments=[NameSpecifier(name="OperatorClass")]
)
)
),
name=PQName(segments=[NameSpecifier(name="operator+")]),
parameters=[],
access="public",
operator="+",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator-")]),
parameters=[],
access="public",
operator="-",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator*")]),
parameters=[],
access="public",
operator="*",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator\\")]),
parameters=[],
access="public",
operator="\\",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator%")]),
parameters=[],
access="public",
operator="%",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator^")]),
parameters=[],
access="public",
operator="^",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator|")]),
parameters=[],
access="public",
operator="|",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator&")]),
parameters=[],
access="public",
operator="&",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator~")]),
parameters=[],
access="public",
operator="~",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator<<")]),
parameters=[],
access="public",
operator="<<",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator>>")]),
parameters=[],
access="public",
operator=">>",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator!=")]),
parameters=[],
access="public",
operator="!=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator<")]),
parameters=[],
access="public",
operator="<",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator>")]),
parameters=[],
access="public",
operator=">",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator>=")]),
parameters=[],
access="public",
operator=">=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator<=")]),
parameters=[],
access="public",
operator="<=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator!")]),
parameters=[],
access="public",
operator="!",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator&&")]),
parameters=[],
access="public",
operator="&&",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator||")]),
parameters=[],
access="public",
operator="||",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator+=")]),
parameters=[],
access="public",
operator="+=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator-=")]),
parameters=[],
access="public",
operator="-=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator*=")]),
parameters=[],
access="public",
operator="*=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator\\=")]),
parameters=[],
access="public",
operator="\\=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator%=")]),
parameters=[],
access="public",
operator="%=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator&=")]),
parameters=[],
access="public",
operator="&=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator|=")]),
parameters=[],
access="public",
operator="|=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator^=")]),
parameters=[],
access="public",
operator="^=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator<<=")]),
parameters=[],
access="public",
operator="<<=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator>>=")]),
parameters=[],
access="public",
operator=">>=",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator++")]),
parameters=[],
access="public",
operator="++",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator--")]),
parameters=[],
access="public",
operator="--",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator()")]),
parameters=[],
access="public",
operator="()",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator->")]),
parameters=[],
access="public",
operator="->",
),
Operator(
return_type=Type(
typename=PQName(
segments=[FundamentalSpecifier(name="void")]
)
),
name=PQName(segments=[NameSpecifier(name="operator,")]),
parameters=[],
access="public",
operator=",",
),
],
)
]
)
)

1290
tests/test_template.py Normal file

File diff suppressed because it is too large Load Diff

52
tests/test_tokfmt.py Normal file
View File

@ -0,0 +1,52 @@
import pytest
from cxxheaderparser.lexer import Lexer
from cxxheaderparser.tokfmt import tokfmt
@pytest.mark.parametrize(
"instr",
[
"int",
"unsigned int",
"::uint8_t",
"void *",
"const char *",
"const char[]",
"void * (*)()",
"void (*)(void * buf, int buflen)",
"void (* fnType)(void * buf, int buflen)",
"TypeName<int, void>& x",
"vector<string>&",
"std::vector<Pointer *> *",
"Alpha::Omega",
"Convoluted::Nested::Mixin",
"std::function<void ()>",
"std::shared_ptr<std::function<void (void)>>",
"tr1::shared_ptr<SnailTemplateClass<SnailNamespace::SnailClass>>",
"std::map<unsigned, std::pair<unsigned, SnailTemplateClass<SnailNamespace::SnailClass>>>",
"std::is_base_of<std::random_access_iterator_tag, IteratorCategoryT>::value",
"const char&&",
"something<T, U>{1, 2, 3}",
"operator-=",
"operator[]",
"operator*",
"operator>=",
],
)
def test_tokfmt(instr):
"""
Each input string is exactly what the output of tokfmt should be
"""
toks = []
lexer = Lexer("")
lexer.input(instr)
while True:
tok = lexer.token_eof_ok()
if not tok:
break
toks.append(tok)
assert tokfmt(toks) == instr

870
tests/test_typedef.py Normal file
View File

@ -0,0 +1,870 @@
# 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():
content = """
typedef std::vector<int> 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(
tokens=[Token(value="int")]
)
]
),
),
]
)
),
name="IntVector",
)
]
)
)
def test_struct_typedef_1():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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",
)
],
)
)

154
tests/test_union.py Normal file
View File

@ -0,0 +1,154 @@
# 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():
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():
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=[FundamentalSpecifier(name="int")]
)
),
name="z",
)
],
)
]
)
)

511
tests/test_using.py Normal file
View File

@ -0,0 +1,511 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
from cxxheaderparser.types import (
BaseClass,
ClassDecl,
Function,
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():
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():
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():
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():
content = """
template <typename T> using alias = foo<T>;
"""
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(tokens=[Token(value="T")])
]
),
)
]
)
),
template=TemplateDecl(
params=[TemplateTypeParam(typekey="typename", name="T")]
),
)
]
)
)
def test_alias_declaration_3():
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():
content = """
template <typename T> using alias = ::foo::bar<T>;
"""
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(tokens=[Token(value="T")])
]
),
),
]
)
),
template=TemplateDecl(
params=[TemplateTypeParam(typekey="typename", name="T")]
),
)
]
)
)
def test_alias_declaration_5():
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():
content = """
template <typename T> using alias = foo<T>::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(tokens=[Token(value="T")])
]
),
),
NameSpecifier(name="bar"),
]
)
),
template=TemplateDecl(
params=[TemplateTypeParam(typekey="typename", name="T")]
),
)
]
)
)
def test_using_many_things():
content = """
// clang-format off
using std::thing;
using MyThing = SomeThing;
namespace a {
using std::string;
using VoidFunction = std::function<void()>;
void fn(string &s, VoidFunction fn, thing * t);
class A : public B {
public:
using B::B;
using IntFunction = std::function<int()>;
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(
tokens=[
Token(value="int"),
Token(value="("),
Token(value=")"),
]
)
]
),
),
]
)
),
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(
tokens=[
Token(value="void"),
Token(value="("),
Token(value=")"),
]
)
]
),
),
]
)
),
)
],
)
},
)
)

768
tests/test_var.py Normal file
View File

@ -0,0 +1,768 @@
# Note: testcases generated via `python -m cxxheaderparser.gentest`
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
def test_var_unixwiz_ridiculous():
# 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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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():
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,
)
]
)
)