diff options
author | Kevin Cheng <kevcheng@google.com> | 2019-04-16 10:24:22 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-04-16 10:24:22 -0700 |
commit | 84b873dae5d524b01ce640e26756b01af97c70cb (patch) | |
tree | 37f5039d6d521b6bb50d81f06edffac384763d0c | |
parent | 775e41a49bd0233ed94f379fd99b5a5837e257b2 (diff) | |
parent | 0b4e91039730a8744315f754d3601a7fb163c69f (diff) | |
download | pycparser-84b873dae5d524b01ce640e26756b01af97c70cb.tar.gz |
Initial commit of pycparser 2.19 am: 2e2b43f3cc am: 43ffef5500
am: 0b4e910397
Change-Id: I453cf9aee8e842342b133b86da3f91fad406b0ed
197 files changed, 22663 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ce5b00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.pyc +tests/parser.out +tests/*tab.py +build +yacctab.py +lextab.py +dist +MANIFEST +*.exe +*.o +parser.out +*.orig +*.rej +.tox +utils/z.c +*.egg-info + diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..25c9df6 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: python +python: + - "2.7" + - "3.4" + - "3.5" + - "3.6" +script: python tests/all_tests.py @@ -0,0 +1,259 @@ ++ Version 2.19 (2018.09.19) + + - PR #277: Fix parsing of floating point literals + - PR #254: Add support for parsing empty structs + - PR #240: Fix enum formatting in generated C code (also #216) + - PR #222: Add support for #pragma in struct declarations + ++ Version 2.18 (2017.07.04) + + - PR #161 & #184: Update bundled PLY version to 3.10 + - PR #158: Add support for the __int128 type. + - PR #169: Handle more tricky TYPEID in declarators. + - PR #178: Add columns to the coord of each node + ++ Version 2.17 (2016.10.29) + + - Again functionality identical to 2.15 and 2.16; the difference is that the + tarball now contains Python files with properly set permissions. + ++ Version 2.16 (2016.10.18) + + - Functionally identical to 2.15, but fixes a packaging problem that caused + failed installation (_build_tables wasn't rerun in the pycparser/ dir). + ++ Version 2.15 (2016.10.18) + + - PR #121: Update bundled PLY version to 3.8 + - Issue #117: Fix parsing of extra semi-colons inside structure declarations. + - PR #109: Update c_generator to add {} around nested named initializers. + - PR #101: Added support for parsing pragmas into the AST. + - Additional fake headers and typedefs, manifest fixes (#97, #106, #111). + - Testing with Python 3.5 instead of 3.3 now (3.4 and 3.5 are the 3.x versions + tested). + - PR #145: More complete support for offsetof() + - Issue #116: Fix line numbers recorded for empty and compound statements. + - Minor performance improvement to the invalid string literal regex. + ++ Version 2.14 (2015.06.09) + + - Added CParser parameter to specify output directory for generated parsing + tables (#84). + - Removed lcc's cpp and its license from the distribution. Using lcc's cpp + is no longer recommended, now that Clang has binary builds available for + Windows. + ++ Version 2.13 (2015.05.12) + + - Added support for offsetof() the way gcc implements it (special builtin + that takes a type as an argument). + - Added faked va_* macros (these are expected to come from stdarg.h) + - Added a bunch more fake headers and typedefs to support parsing C projects + like Git and SQLite without modifications to pycparser. + - Added support for empty initializer lists (#79). + ++ Version 2.12 (2015.04.21) + + - This is a fix release for 2.11; the memory optimization with __slots__ on + Coord and AST nodes didn't take weakrefs into account, which broke cffi and + its many dependents (issue #76). Fixed by adding __weakref__ to __slots__. + ++ Version 2.11 (2015.04.21) + + - Add support for C99 6.5.3.7 p7 - qualifiers within array dimensions in + function declarations. Started with issue #21 (reported with initial patch + by Robin Martinjak). + - Issue #27: bug in handling of unified wstring literals. + - Issue #28: fix coord reporting for 'for' loops. + - Added ``examples/using_gcc_E_libc.py`` to demonstrate how ``gcc -E`` can + be used instead of ``cpp`` for preprocessing. + - Pull request #64: support keywords like const, volatile, restrict and static + in dimensions in array declarations. + - Reduce memory usage of AST nodes (issue #72). + - Parsing order of nested pointer declarations fixed (issue #68). + ++ Version 2.10 (2013.08.03) + + - A number of improvements in the handling of typedef-name ambiguities, + contributed by Sye van der Veen in GitHub issue #1: + + * Allow shadowing of types by identifiers in inner scopes. + * Allow struct field names to reside in a separate namespace and have + the same names as types. + * Allow duplicate typedefs in some cases to mimic real compiler behavior. + + - c_generator error for ExprList in expression context. + - Assume default int type for functions whose argument or return types were + not specified. + - Relax the lexer a bit w.r.t. some integer suffixes and $ in identifier names + (which is supported by some other compilers). + ++ Version 2.09.1 (2012.12.29) + + - No actual functionality changes. + - The source distribution was re-packaged to contain the pre-generated Lex and + Yacc tables of PLY. + ++ Version 2.09 (2012.12.27) + + - The pycparser project has moved to Bitbucket. For this version, issue + numbers still refer to the old Googlecode project, unless stated otherwise. + Starting with the next version all issue numbers will refer to the new + Bitbucket project. + - pycparser now carries its PLY dependency along. The pycparser/ply directory + contains the source of PLY for the currently supported version. This makes + distribution and testing easier. + - Issue #79: fix generation of new switch/case AST nodes. + - Issue #83: fix parsing and C generation to distinguish between initializer + lists in declarations and initializing variables with parenthesized + comma-separated expressions. + - Issue #84: fix C generation for some statements. + - Issues #86 and #87: improve location reporting for parse errors. + - Issue #89: fix C generation for K&R-style function definitions. + ++ Version 2.08 (2012.08.10) + + - Issue 73: initial support for #pragma directives. Consume them without + errors and ignore (no tokens are returned). Line numbers are preserved. + - Issue 68: more correct handling of source files without any actual content. + - Issue 69: running all tests will now set appropriate return code. + - Better error reporting in case where multiple type specifiers are provided. + Also fixes Issue 60. + - Issue 63: line endings cleanup for consistent LF ending. + - Issues 64 & 65: added some more headers and typedefs to fake includes. + - Refactoring the cpp invocation in parse_file into a separate function, which + can also be used as a utility. + - Issue 74: some Windows include paths were handled incorrectly. + ++ Version 2.07 (2012.06.16) + + - Issue 54: added an optional parser argument to parse_file + - Issue 59: added some more fake headers for C99 + - Issue 62: correct coord for Ellipsis nodes + - Issue 57: support for C99 hexadecimal float constants + - Made running tests that call on 'cpp' a bit more robust. + ++ Version 2.06 (2012.02.04) + + - Issue 48: gracefully handle parsing of empty files + - Issues 49 & 50: handle more escaped chars in paths to #line - "..\..\test.h". + - Support for C99 _Complex type. + - CGenerator moves from examples/ to pycparser/ as a first-class citizen, and + added some fixes to it. examples/c-to-c.py still stays as a convenience + wrapper. + - Fix problem with parsing a file in which the first statement is just a + semicolon. + - Improved the AST created for switch statements, making it closer to the + semantic meaning than to the grammar. + ++ Version 2.05 (2011.10.16) + + - Added support for the C99 ``_Bool`` type and ``stdbool.h`` header file + - Expanded ``examples/explore_ast.py`` with more details on working with the + AST + - Relaxed the rules on parsing unnamed struct members (helps parse ``windows.h``) + - Bug fixes: + + * Fixed spacing issue for some type declarations + * Issue 47: display empty statements (lone ';') correctly after parsing + ++ Version 2.04 (2011.05.21) + + - License changed from LGPL to BSD + - Bug fixes: + + * Issue 31: constraining the scope of typedef definitions + * Issues 33, 35: fixes for the c-to-c.py example + + - Added C99 integer types to fake headers + - Added unit tests for the c-to-c.py example + ++ Version 2.03 (2011.03.06) + + - Bug fixes: + + * Issue 17: empty file-level declarations + * Issue 18: empty statements and declarations in functions + * Issue 19: anonymous structs & union fields + * Issue 23: fix coordinates of Cast nodes + + - New example added (``examples/c-to-c.py``) for translating ASTs generated + by ``pycparser`` back into C code. + - ``pycparser`` is now on PyPI (Python Package Index) + - Created `FAQ <http://code.google.com/p/pycparser/wiki/FAQ>`_ on + the ``pycparser`` project page + - Removed support for Python 2.5. ``pycparser`` supports Python 2 + from 2.6 and on, and Python 3. + ++ Version 2.02 (2010.12.10) + + * The name of a ``NamedInitializer`` node was turned into a sequence of nodes + instead of an attribute, to make it discoverable by the AST node visitor. + * Documentation updates + ++ Version 2.01 (04.12.2010) + + * Removed dependency on YAML. Parsing of the AST node configuration file + is done with a simple parser. + * Fixed issue 12: installation problems + ++ Version 2.00 (2010.10.31) + + * Support for C99 (read + `this wiki page <http://code.google.com/p/pycparser/wiki/C99support>`_ + for more information). + ++ Version 1.08 (2010.10.09) + + * Bug fixes: + + + Correct handling of ``do{} ... while`` statements in some cases + + Issues 6 & 7: Concatenation of string literals + + Issue 9: Support for unnamed bitfields in structs + ++ Version 1.07 (2010.05.18) + + * Python 3.1 compatibility: ``pycparser`` was modified to run + on Python 3.1 as well as 2.6 + ++ Version 1.06 (2010.04.10) + + * Bug fixes: + + + coord not propagated to FuncCall nodes + + lexing of the ^= token (XOREQUALS) + + parsing failed on some abstract declarator rules + + * Linux compatibility: fixed end-of-line and ``cpp`` path issues to allow + all tests and examples run on Linux + ++ Version 1.05 (2009.10.16) + + * Fixed the ``parse_file`` auxiliary function to handle multiple arguments to + ``cpp`` correctly + ++ Version 1.04 (2009.05.22) + + * Added the ``fake_libc_include`` directory to allow parsing of C code that + uses standard C library include files without dependency on a real C + library. + * Tested with Python 2.6 and PLY 3.2 + ++ Version 1.03 (2009.01.31) + + * Accept enumeration lists with a comma after the last item (C99 feature). + ++ Version 1.02 (2009.01.16) + + * Fixed problem of parsing struct/enum/union names that were named similarly + to previously defined ``typedef`` types. + ++ Version 1.01 (2009.01.09) + + * Fixed subprocess invocation in the helper function parse_file - now + it's more portable + ++ Version 1.0 (2008.11.15) + + * Initial release + * Support for ANSI C89 diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..481737f --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,25 @@ +This is a list of people who have contributed to pycparser by supplying patches, +opening issues, or generally helping out, before the project moved to Github. + +For more recent contributions, check out the "Contributors" page of the +pycparser Github project. + +The list is sorted in increasing alphabetic order by first name. + +* Andreas Klöckner +* Andrew de los Reyes +* Benoit Pradelle +* Dov Feldstern +* Even Wiik Thomassen +* Greg Smith +* Jaroslav Franek +* Li Xuan Ji +* Mateusz Czaplinski +* Paolo Di Maio +* Rory Yorke +* Rubin +* Scott Tsai +* Sye van der Veen +* Thomas W. Barr +* Tomer Segal +* Weyllor @@ -0,0 +1,27 @@ +pycparser -- A C parser in Python + +Copyright (c) 2008-2017, Eli Bendersky +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of Eli Bendersky 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. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..8017b7e --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,12 @@ +recursive-include examples *.c *.h *.py +recursive-include tests *.c *.h *.py +recursive-include pycparser *.py *.cfg +recursive-include utils/fake_libc_include *.h +include README.* +include LICENSE +include CHANGES +include setup.* +exclude setup.pyc + +recursive-exclude tests yacctab.* lextab.* +recursive-exclude examples yacctab.* lextab.* diff --git a/METADATA b/METADATA new file mode 100644 index 0000000..969313e --- /dev/null +++ b/METADATA @@ -0,0 +1,19 @@ +name: "pycparser" +description: + "pycparser is a parser for the C language, written in pure Python. It is a " + "module designed to be easily integrated into applications that need to " + "parse C source code." + +third_party { + url { + type: HOMEPAGE + value: "https://github.com/eliben/pycparser" + } + url { + type: GIT + value: "https://github.com/eliben/pycparser" + } + version: "2.19" + last_upgrade_date { year: 2019 month: 2 day: 26 } + license_type: NOTICE +} diff --git a/MODULE_LICENSE_MIT b/MODULE_LICENSE_MIT new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/MODULE_LICENSE_MIT @@ -0,0 +1 @@ +LICENSE
\ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..df9025c --- /dev/null +++ b/README.rst @@ -0,0 +1,257 @@ +=============== +pycparser v2.19 +=============== + +:Author: `Eli Bendersky <https://eli.thegreenplace.net/>`_ + + +.. contents:: + :backlinks: none + +.. sectnum:: + + +Introduction +============ + +What is pycparser? +------------------ + +**pycparser** is a parser for the C language, written in pure Python. It is a +module designed to be easily integrated into applications that need to parse +C source code. + +What is it good for? +-------------------- + +Anything that needs C code to be parsed. The following are some uses for +**pycparser**, taken from real user reports: + +* C code obfuscator +* Front-end for various specialized C compilers +* Static code checker +* Automatic unit-test discovery +* Adding specialized extensions to the C language + +One of the most popular uses of **pycparser** is in the `cffi +<https://cffi.readthedocs.io/en/latest/>`_ library, which uses it to parse the +declarations of C functions and types in order to auto-generate FFIs. + +**pycparser** is unique in the sense that it's written in pure Python - a very +high level language that's easy to experiment with and tweak. To people familiar +with Lex and Yacc, **pycparser**'s code will be simple to understand. It also +has no external dependencies (except for a Python interpreter), making it very +simple to install and deploy. + +Which version of C does pycparser support? +------------------------------------------ + +**pycparser** aims to support the full C99 language (according to the standard +ISO/IEC 9899). Some features from C11 are also supported, and patches to support +more are welcome. + +**pycparser** supports very few GCC extensions, but it's fairly easy to set +things up so that it parses code with a lot of GCC-isms successfully. See the +`FAQ <https://github.com/eliben/pycparser/wiki/FAQ>`_ for more details. + +What grammar does pycparser follow? +----------------------------------- + +**pycparser** very closely follows the C grammar provided in Annex A of the C99 +standard (ISO/IEC 9899). + +How is pycparser licensed? +-------------------------- + +`BSD license <https://github.com/eliben/pycparser/blob/master/LICENSE>`_. + +Contact details +--------------- + +For reporting problems with **pycparser** or submitting feature requests, please +open an `issue <https://github.com/eliben/pycparser/issues>`_, or submit a +pull request. + + +Installing +========== + +Prerequisites +------------- + +* **pycparser** was tested on Python 2.7, 3.4-3.6, on both Linux and + Windows. It should work on any later version (in both the 2.x and 3.x lines) + as well. + +* **pycparser** has no external dependencies. The only non-stdlib library it + uses is PLY, which is bundled in ``pycparser/ply``. The current PLY version is + 3.10, retrieved from `<http://www.dabeaz.com/ply/>`_ + +Note that **pycparser** (and PLY) uses docstrings for grammar specifications. +Python installations that strip docstrings (such as when using the Python +``-OO`` option) will fail to instantiate and use **pycparser**. You can try to +work around this problem by making sure the PLY parsing tables are pre-generated +in normal mode; this isn't an officially supported/tested mode of operation, +though. + +Installation process +-------------------- + +Installing **pycparser** is very simple. Once you download and unzip the +package, you just have to execute the standard ``python setup.py install``. The +setup script will then place the ``pycparser`` module into ``site-packages`` in +your Python's installation library. + +Alternatively, since **pycparser** is listed in the `Python Package Index +<https://pypi.org/project/pycparser/>`_ (PyPI), you can install it using your +favorite Python packaging/distribution tool, for example with:: + + > pip install pycparser + +Known problems +-------------- + +* Some users who've installed a new version of **pycparser** over an existing + version ran into a problem using the newly installed library. This has to do + with parse tables staying around as ``.pyc`` files from the older version. If + you see unexplained errors from **pycparser** after an upgrade, remove it (by + deleting the ``pycparser`` directory in your Python's ``site-packages``, or + wherever you installed it) and install again. + + +Using +===== + +Interaction with the C preprocessor +----------------------------------- + +In order to be compilable, C code must be preprocessed by the C preprocessor - +``cpp``. ``cpp`` handles preprocessing directives like ``#include`` and +``#define``, removes comments, and performs other minor tasks that prepare the C +code for compilation. + +For all but the most trivial snippets of C code **pycparser**, like a C +compiler, must receive preprocessed C code in order to function correctly. If +you import the top-level ``parse_file`` function from the **pycparser** package, +it will interact with ``cpp`` for you, as long as it's in your PATH, or you +provide a path to it. + +Note also that you can use ``gcc -E`` or ``clang -E`` instead of ``cpp``. See +the ``using_gcc_E_libc.py`` example for more details. Windows users can download +and install a binary build of Clang for Windows `from this website +<http://llvm.org/releases/download.html>`_. + +What about the standard C library headers? +------------------------------------------ + +C code almost always ``#include``\s various header files from the standard C +library, like ``stdio.h``. While (with some effort) **pycparser** can be made to +parse the standard headers from any C compiler, it's much simpler to use the +provided "fake" standard includes in ``utils/fake_libc_include``. These are +standard C header files that contain only the bare necessities to allow valid +parsing of the files that use them. As a bonus, since they're minimal, it can +significantly improve the performance of parsing large C files. + +The key point to understand here is that **pycparser** doesn't really care about +the semantics of types. It only needs to know whether some token encountered in +the source is a previously defined type. This is essential in order to be able +to parse C correctly. + +See `this blog post +<https://eli.thegreenplace.net/2015/on-parsing-c-type-declarations-and-fake-headers>`_ +for more details. + +Basic usage +----------- + +Take a look at the |examples|_ directory of the distribution for a few examples +of using **pycparser**. These should be enough to get you started. Please note +that most realistic C code samples would require running the C preprocessor +before passing the code to **pycparser**; see the previous sections for more +details. + +.. |examples| replace:: ``examples`` +.. _examples: examples + + +Advanced usage +-------------- + +The public interface of **pycparser** is well documented with comments in +``pycparser/c_parser.py``. For a detailed overview of the various AST nodes +created by the parser, see ``pycparser/_c_ast.cfg``. + +There's also a `FAQ available here <https://github.com/eliben/pycparser/wiki/FAQ>`_. +In any case, you can always drop me an `email <eliben@gmail.com>`_ for help. + + +Modifying +========= + +There are a few points to keep in mind when modifying **pycparser**: + +* The code for **pycparser**'s AST nodes is automatically generated from a + configuration file - ``_c_ast.cfg``, by ``_ast_gen.py``. If you modify the AST + configuration, make sure to re-generate the code. +* Make sure you understand the optimized mode of **pycparser** - for that you + must read the docstring in the constructor of the ``CParser`` class. For + development you should create the parser without optimizations, so that it + will regenerate the Yacc and Lex tables when you change the grammar. + + +Package contents +================ + +Once you unzip the ``pycparser`` package, you'll see the following files and +directories: + +README.rst: + This README file. + +LICENSE: + The pycparser license + +setup.py: + Installation script + +examples/: + A directory with some examples of using **pycparser** + +pycparser/: + The **pycparser** module source code. + +tests/: + Unit tests. + +utils/fake_libc_include: + Minimal standard C library include files that should allow to parse any C code. + +utils/internal/: + Internal utilities for my own use. You probably don't need them. + + +Contributors +============ + +Some people have contributed to **pycparser** by opening issues on bugs they've +found and/or submitting patches. The list of contributors is in the CONTRIBUTORS +file in the source distribution. After **pycparser** moved to Github I stopped +updating this list because Github does a much better job at tracking +contributions. + + +CI Status +========= + +**pycparser** has automatic testing enabled through the convenient +`Travis CI project <https://travis-ci.org>`_. Here is the latest build status: + +.. image:: https://travis-ci.org/eliben/pycparser.png?branch=master + :align: center + :target: https://travis-ci.org/eliben/pycparser + +AppVeyor also helps run tests on Windows: + +.. image:: https://ci.appveyor.com/api/projects/status/wrup68o5y8nuk1i9?svg=true + :align: center + :target: https://ci.appveyor.com/project/eliben/pycparser/ diff --git a/TODO.txt b/TODO.txt new file mode 100644 index 0000000..3737893 --- /dev/null +++ b/TODO.txt @@ -0,0 +1,43 @@ +Todo +---- + +Version Update +-------------- + +setup.py, __init__.py, README, CHANGES +- Make sure _build_tables was run in pycparser/ +- If PLY version changes, note it in README and ply/LICENSE +- Run tox tests + +python setup.py sdist + +Copy the newly created tarball and untar it; check that the permissions of +Python files in the pycparser/ directory are OK. Check to see that lextab.py and +yacctab.py are there. + +Now create a new virtualenv and in it install the tarball with +`pip install <tarballname>`. See that pycparser is importable in the Python +interpreter of this virtualenv; run pycparser tests from this virtualenv. + +After this it's OK to rerun `python3.6 setup.py sdist upload` to push to PyPI +(older Pythons use a deprecated API for PyPI uploading). + +- Tag in git. When pushing to GitHub, git push --tags + +Misc +---- + +yacc optimization: +- If parsetab.py/pyc doesn't exist in the path, the table will be reconstructed + anyway, regardless of the optimize parameter +- If it does exist: + - If optimize=True, the table will be loaded unconditionally + - If optimize=False, the table will be loaded only if it's older than the + grammar + +lex optimization: +- If optimize=False, the lexical table is re-computed and is not saved to a + lextab file +- If optimize=True: + - If lextab.py/pyc exists in the path, it will be loaded unconditionally + - If lextab.py/pyc doesn't exist, it will be created and loaded diff --git a/_clean_tables.py b/_clean_tables.py new file mode 100644 index 0000000..5b0f1c3 --- /dev/null +++ b/_clean_tables.py @@ -0,0 +1,27 @@ +# Cleanup all tables and PYC files to ensure no PLY stuff is cached +from __future__ import print_function +import itertools +import fnmatch +import os, shutil + +file_patterns = ('yacctab.*', 'lextab.*', '*.pyc', '__pycache__') + + +def do_cleanup(root): + for path, dirs, files in os.walk(root): + for file in itertools.chain(dirs, files): + try: + for pattern in file_patterns: + if fnmatch.fnmatch(file, pattern): + fullpath = os.path.join(path, file) + if os.path.isdir(fullpath): + shutil.rmtree(fullpath, ignore_errors=False) + else: + os.unlink(fullpath) + print('Deleted', fullpath) + except OSError: + pass + + +if __name__ == "__main__": + do_cleanup('.') diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..05c1c7e --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,12 @@ +environment: + + matrix: + - PYTHON: "C:\\Python27" + - PYTHON: "C:\\Python34" + - PYTHON: "C:\\Python35" + - PYTHON: "C:\\Python36" + +build: off + +test_script: + - "%PYTHON%\\python.exe tests\\all_tests.py" diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..048f5a4 --- /dev/null +++ b/examples/README.rst @@ -0,0 +1,8 @@ +Run these examples from the root directory of pycparser. + +Please note that most realistic C code samples would require running the C +preprocessor before passing the code to **pycparser**; see the `README file +<https://github.com/eliben/pycparser/blob/master/README.rst>`_ and +`this blog post +<https://eli.thegreenplace.net/2015/on-parsing-c-type-declarations-and-fake-headers>`_ +more details. diff --git a/examples/c-to-c.py b/examples/c-to-c.py new file mode 100644 index 0000000..cc14598 --- /dev/null +++ b/examples/c-to-c.py @@ -0,0 +1,62 @@ +#------------------------------------------------------------------------------ +# pycparser: c-to-c.py +# +# Example of using pycparser.c_generator, serving as a simplistic translator +# from C to AST and back to C. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------ +from __future__ import print_function +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import parse_file, c_parser, c_generator + + +def translate_to_c(filename): + """ Simply use the c_generator module to emit a parsed AST. + """ + ast = parse_file(filename, use_cpp=True) + generator = c_generator.CGenerator() + print(generator.visit(ast)) + + +def _zz_test_translate(): + # internal use + src = r''' + + void f(char * restrict joe){} + +int main(void) +{ + unsigned int long k = 4; + int p = - - k; + return 0; +} +''' + parser = c_parser.CParser() + ast = parser.parse(src) + ast.show() + generator = c_generator.CGenerator() + + print(generator.visit(ast)) + + # tracing the generator for debugging + #~ import trace + #~ tr = trace.Trace(countcallers=1) + #~ tr.runfunc(generator.visit, ast) + #~ tr.results().write_results() + + +#------------------------------------------------------------------------------ +if __name__ == "__main__": + #_zz_test_translate() + if len(sys.argv) > 1: + translate_to_c(sys.argv[1]) + else: + print("Please provide a filename as argument") diff --git a/examples/c_files/funky.c b/examples/c_files/funky.c new file mode 100644 index 0000000..5ebf7b2 --- /dev/null +++ b/examples/c_files/funky.c @@ -0,0 +1,20 @@ +char foo(void) +{ + return '1'; +} + +int maxout_in(int paste, char** matrix) +{ + char o = foo(); + return (int) matrix[1][2] * 5 - paste; +} + +int main() +{ + auto char* multi = "a multi"; + + +} + + + diff --git a/examples/c_files/hash.c b/examples/c_files/hash.c new file mode 100644 index 0000000..c11fe45 --- /dev/null +++ b/examples/c_files/hash.c @@ -0,0 +1,200 @@ +/* +** C implementation of a hash table ADT +*/ +typedef enum tagReturnCode {SUCCESS, FAIL} ReturnCode; + + +typedef struct tagEntry +{ + char* key; + char* value; +} Entry; + + + +typedef struct tagNode +{ + Entry* entry; + + struct tagNode* next; +} Node; + + +typedef struct tagHash +{ + unsigned int table_size; + + Node** heads; + +} Hash; + + +static unsigned int hash_func(const char* str, unsigned int table_size) +{ + unsigned int hash_value; + unsigned int a = 127; + + for (hash_value = 0; *str != 0; ++str) + hash_value = (a*hash_value + *str) % table_size; + + return hash_value; +} + + +ReturnCode HashCreate(Hash** hash, unsigned int table_size) +{ + unsigned int i; + + if (table_size < 1) + return FAIL; + + // + // Allocate space for the Hash + // + if (((*hash) = malloc(sizeof(**hash))) == NULL) + return FAIL; + + // + // Allocate space for the array of list heads + // + if (((*hash)->heads = malloc(table_size*sizeof(*((*hash)->heads)))) == NULL) + return FAIL; + + // + // Initialize Hash info + // + for (i = 0; i < table_size; ++i) + { + (*hash)->heads[i] = NULL; + } + + (*hash)->table_size = table_size; + + return SUCCESS; +} + + +ReturnCode HashInsert(Hash* hash, const Entry* entry) +{ + unsigned int index = hash_func(entry->key, hash->table_size); + Node* temp = hash->heads[index]; + + HashRemove(hash, entry->key); + + if ((hash->heads[index] = malloc(sizeof(Node))) == NULL) + return FAIL; + + hash->heads[index]->entry = malloc(sizeof(Entry)); + hash->heads[index]->entry->key = malloc(strlen(entry->key)+1); + hash->heads[index]->entry->value = malloc(strlen(entry->value)+1); + strcpy(hash->heads[index]->entry->key, entry->key); + strcpy(hash->heads[index]->entry->value, entry->value); + + hash->heads[index]->next = temp; + + return SUCCESS; +} + + + +const Entry* HashFind(const Hash* hash, const char* key) +{ + unsigned int index = hash_func(key, hash->table_size); + Node* temp = hash->heads[index]; + + while (temp != NULL) + { + if (!strcmp(key, temp->entry->key)) + return temp->entry; + + temp = temp->next; + } + + return NULL; +} + + +ReturnCode HashRemove(Hash* hash, const char* key) +{ + unsigned int index = hash_func(key, hash->table_size); + Node* temp1 = hash->heads[index]; + Node* temp2 = temp1; + + while (temp1 != NULL) + { + if (!strcmp(key, temp1->entry->key)) + { + if (temp1 == hash->heads[index]) + hash->heads[index] = hash->heads[index]->next; + else + temp2->next = temp1->next; + + free(temp1->entry->key); + free(temp1->entry->value); + free(temp1->entry); + free(temp1); + temp1 = NULL; + + return SUCCESS; + } + + temp2 = temp1; + temp1 = temp1->next; + } + + return FAIL; +} + + +void HashPrint(Hash* hash, void (*PrintFunc)(char*, char*)) +{ + unsigned int i; + + if (hash == NULL || hash->heads == NULL) + return; + + for (i = 0; i < hash->table_size; ++i) + { + Node* temp = hash->heads[i]; + + while (temp != NULL) + { + PrintFunc(temp->entry->key, temp->entry->value); + temp = temp->next; + } + } +} + + + +void HashDestroy(Hash* hash) +{ + unsigned int i; + + if (hash == NULL) + return; + + for (i = 0; i < hash->table_size; ++i) + { + Node* temp = hash->heads[i]; + + while (temp != NULL) + { + Node* temp2 = temp; + + free(temp->entry->key); + free(temp->entry->value); + free(temp->entry); + + temp = temp->next; + + free(temp2); + } + } + + free(hash->heads); + hash->heads = NULL; + + free(hash); +} + diff --git a/examples/c_files/memmgr.c b/examples/c_files/memmgr.c new file mode 100644 index 0000000..d9bc290 --- /dev/null +++ b/examples/c_files/memmgr.c @@ -0,0 +1,206 @@ +//---------------------------------------------------------------- +// Statically-allocated memory manager +// +// by Eli Bendersky (eliben@gmail.com) +// +// This code is in the public domain. +//---------------------------------------------------------------- +#include "memmgr.h" + +typedef ulong Align; + +union mem_header_union +{ + struct + { + // Pointer to the next block in the free list + // + union mem_header_union* next; + + // Size of the block (in quantas of sizeof(mem_header_t)) + // + ulong size; + } s; + + // Used to align headers in memory to a boundary + // + Align align_dummy; +}; + +typedef union mem_header_union mem_header_t; + +// Initial empty list +// +static mem_header_t base; + +// Start of free list +// +static mem_header_t* freep = 0; + +// Static pool for new allocations +// +static byte pool[POOL_SIZE] = {0}; +static ulong pool_free_pos = 0; + + +void memmgr_init() +{ + base.s.next = 0; + base.s.size = 0; + freep = 0; + pool_free_pos = 0; +} + + +static mem_header_t* get_mem_from_pool(ulong nquantas) +{ + ulong total_req_size; + + mem_header_t* h; + + if (nquantas < MIN_POOL_ALLOC_QUANTAS) + nquantas = MIN_POOL_ALLOC_QUANTAS; + + total_req_size = nquantas * sizeof(mem_header_t); + + if (pool_free_pos + total_req_size <= POOL_SIZE) + { + h = (mem_header_t*) (pool + pool_free_pos); + h->s.size = nquantas; + memmgr_free((void*) (h + 1)); + pool_free_pos += total_req_size; + } + else + { + return 0; + } + + return freep; +} + + +// Allocations are done in 'quantas' of header size. +// The search for a free block of adequate size begins at the point 'freep' +// where the last block was found. +// If a too-big block is found, it is split and the tail is returned (this +// way the header of the original needs only to have its size adjusted). +// The pointer returned to the user points to the free space within the block, +// which begins one quanta after the header. +// +void* memmgr_alloc(ulong nbytes) +{ + mem_header_t* p; + mem_header_t* prevp; + + // Calculate how many quantas are required: we need enough to house all + // the requested bytes, plus the header. The -1 and +1 are there to make sure + // that if nbytes is a multiple of nquantas, we don't allocate too much + // + ulong nquantas = (nbytes + sizeof(mem_header_t) - 1) / sizeof(mem_header_t) + 1; + + // First alloc call, and no free list yet ? Use 'base' for an initial + // denegerate block of size 0, which points to itself + // + if ((prevp = freep) == 0) + { + base.s.next = freep = prevp = &base; + base.s.size = 0; + } + + for (p = prevp->s.next; ; prevp = p, p = p->s.next) + { + // big enough ? + if (p->s.size >= nquantas) + { + // exactly ? + if (p->s.size == nquantas) + { + // just eliminate this block from the free list by pointing + // its prev's next to its next + // + prevp->s.next = p->s.next; + } + else // too big + { + p->s.size -= nquantas; + p += p->s.size; + p->s.size = nquantas; + } + + freep = prevp; + return (void*) (p + 1); + } + // Reached end of free list ? + // Try to allocate the block from the pool. If that succeeds, + // get_mem_from_pool adds the new block to the free list and + // it will be found in the following iterations. If the call + // to get_mem_from_pool doesn't succeed, we've run out of + // memory + // + else if (p == freep) + { + if ((p = get_mem_from_pool(nquantas)) == 0) + { + #ifdef DEBUG_MEMMGR_FATAL + printf("!! Memory allocation failed !!\n"); + #endif + return 0; + } + } + } +} + + +// Scans the free list, starting at freep, looking the the place to insert the +// free block. This is either between two existing blocks or at the end of the +// list. In any case, if the block being freed is adjacent to either neighbor, +// the adjacent blocks are combined. +// +void memmgr_free(void* ap) +{ + mem_header_t* block; + mem_header_t* p; + + // acquire pointer to block header + block = ((mem_header_t*) ap) - 1; + + // Find the correct place to place the block in (the free list is sorted by + // address, increasing order) + // + for (p = freep; !(block > p && block < p->s.next); p = p->s.next) + { + // Since the free list is circular, there is one link where a + // higher-addressed block points to a lower-addressed block. + // This condition checks if the block should be actually + // inserted between them + // + if (p >= p->s.next && (block > p || block < p->s.next)) + break; + } + + // Try to combine with the higher neighbor + // + if (block + block->s.size == p->s.next) + { + block->s.size += p->s.next->s.size; + block->s.next = p->s.next->s.next; + } + else + { + block->s.next = p->s.next; + } + + // Try to combine with the lower neighbor + // + if (p + p->s.size == block) + { + p->s.size += block->s.size; + p->s.next = block->s.next; + } + else + { + p->s.next = block; + } + + freep = p; +} diff --git a/examples/c_files/memmgr.h b/examples/c_files/memmgr.h new file mode 100644 index 0000000..e792fb8 --- /dev/null +++ b/examples/c_files/memmgr.h @@ -0,0 +1,96 @@ +//---------------------------------------------------------------- +// Statically-allocated memory manager +// +// by Eli Bendersky (eliben@gmail.com) +// +// This code is in the public domain. +//---------------------------------------------------------------- +#ifndef MEMMGR_H +#define MEMMGR_H + +// +// Memory manager: dynamically allocates memory from +// a fixed pool that is allocated statically at link-time. +// +// Usage: after calling memmgr_init() in your +// initialization routine, just use memmgr_alloc() instead +// of malloc() and memmgr_free() instead of free(). +// Naturally, you can use the preprocessor to define +// malloc() and free() as aliases to memmgr_alloc() and +// memmgr_free(). This way the manager will be a drop-in +// replacement for the standard C library allocators, and can +// be useful for debugging memory allocation problems and +// leaks. +// +// Preprocessor flags you can define to customize the +// memory manager: +// +// DEBUG_MEMMGR_FATAL +// Allow printing out a message when allocations fail +// +// DEBUG_MEMMGR_SUPPORT_STATS +// Allow printing out of stats in function +// memmgr_print_stats When this is disabled, +// memmgr_print_stats does nothing. +// +// Note that in production code on an embedded system +// you'll probably want to keep those undefined, because +// they cause printf to be called. +// +// POOL_SIZE +// Size of the pool for new allocations. This is +// effectively the heap size of the application, and can +// be changed in accordance with the available memory +// resources. +// +// MIN_POOL_ALLOC_QUANTAS +// Internally, the memory manager allocates memory in +// quantas roughly the size of two ulong objects. To +// minimize pool fragmentation in case of multiple allocations +// and deallocations, it is advisable to not allocate +// blocks that are too small. +// This flag sets the minimal ammount of quantas for +// an allocation. If the size of a ulong is 4 and you +// set this flag to 16, the minimal size of an allocation +// will be 4 * 2 * 16 = 128 bytes +// If you have a lot of small allocations, keep this value +// low to conserve memory. If you have mostly large +// allocations, it is best to make it higher, to avoid +// fragmentation. +// +// Notes: +// 1. This memory manager is *not thread safe*. Use it only +// for single thread/task applications. +// + +#define DEBUG_MEMMGR_SUPPORT_STATS 1 + +#define POOL_SIZE 8 * 1024 +#define MIN_POOL_ALLOC_QUANTAS 16 + + +typedef unsigned char byte; +typedef unsigned long ulong; + + + +// Initialize the memory manager. This function should be called +// only once in the beginning of the program. +// +void memmgr_init(); + +// 'malloc' clone +// +void* memmgr_alloc(ulong nbytes); + +// 'free' clone +// +void memmgr_free(void* ap); + +// Prints statistics about the current state of the memory +// manager +// +void memmgr_print_stats(); + + +#endif // MEMMGR_H diff --git a/examples/c_files/year.c b/examples/c_files/year.c new file mode 100644 index 0000000..12c4a33 --- /dev/null +++ b/examples/c_files/year.c @@ -0,0 +1,53 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +void convert(int thousands, int hundreds, int tens, int ones) +{ +char *num[] = {"", "One", "Two", "Three", "Four", "Five", "Six", + "Seven", "Eight", "Nine"}; + +char *for_ten[] = {"", "", "Twenty", "Thirty", "Fourty", "Fifty", "Sixty", + "Seventy", "Eighty", "Ninty"}; + +char *af_ten[] = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", + "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Ninteen"}; + + printf("\nThe year in words is:\n"); + + printf("%s thousand", num[thousands]); + if (hundreds != 0) + printf(" %s hundred", num[hundreds]); + + if (tens != 1) + printf(" %s %s", for_ten[tens], num[ones]); + else + printf(" %s", af_ten[ones]); +} + + +int main() +{ +int year; +int n1000, n100, n10, n1; + + printf("\nEnter the year (4 digits): "); + scanf("%d", &year); + + if (year > 9999 || year < 1000) + { + printf("\nError !! The year must contain 4 digits."); + exit(EXIT_FAILURE); + } + + n1000 = year/1000; + n100 = ((year)%1000)/100; + n10 = (year%100)/10; + n1 = ((year%10)%10); + + convert(n1000, n100, n10, n1); + +return 0; +} + + diff --git a/examples/c_json.py b/examples/c_json.py new file mode 100644 index 0000000..2b33360 --- /dev/null +++ b/examples/c_json.py @@ -0,0 +1,203 @@ +#------------------------------------------------------------------------------ +# pycparser: c_json.py +# +# by Michael White (@mypalmike) +# +# This example includes functions to serialize and deserialize an ast +# to and from json format. Serializing involves walking the ast and converting +# each node from a python Node object into a python dict. Deserializing +# involves the opposite conversion, walking the tree formed by the +# dict and converting each dict into the specific Node object it represents. +# The dict itself is serialized and deserialized using the python json module. +# +# The dict representation is a fairly direct transformation of the object +# attributes. Each node in the dict gets one metadata field referring to the +# specific node class name, _nodetype. Each local attribute (i.e. not linking +# to child nodes) has a string value or array of string values. Each child +# attribute is either another dict or an array of dicts, exactly as in the +# Node object representation. The "coord" attribute, representing the +# node's location within the source code, is serialized/deserialized from +# a Coord object into a string of the format "filename:line[:column]". +# +# Example TypeDecl node, with IdentifierType child node, represented as a dict: +# "type": { +# "_nodetype": "TypeDecl", +# "coord": "c_files/funky.c:8", +# "declname": "o", +# "quals": [], +# "type": { +# "_nodetype": "IdentifierType", +# "coord": "c_files/funky.c:8", +# "names": [ +# "char" +# ] +# } +# } +#------------------------------------------------------------------------------ +from __future__ import print_function + +import json +import sys +import re + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import parse_file, c_ast +from pycparser.plyparser import Coord + + +RE_CHILD_ARRAY = re.compile(r'(.*)\[(.*)\]') +RE_INTERNAL_ATTR = re.compile('__.*__') + + +class CJsonError(Exception): + pass + + +def memodict(fn): + """ Fast memoization decorator for a function taking a single argument """ + class memodict(dict): + def __missing__(self, key): + ret = self[key] = fn(key) + return ret + return memodict().__getitem__ + + +@memodict +def child_attrs_of(klass): + """ + Given a Node class, get a set of child attrs. + Memoized to avoid highly repetitive string manipulation + + """ + non_child_attrs = set(klass.attr_names) + all_attrs = set([i for i in klass.__slots__ if not RE_INTERNAL_ATTR.match(i)]) + return all_attrs - non_child_attrs + + +def to_dict(node): + """ Recursively convert an ast into dict representation. """ + klass = node.__class__ + + result = {} + + # Metadata + result['_nodetype'] = klass.__name__ + + # Local node attributes + for attr in klass.attr_names: + result[attr] = getattr(node, attr) + + # Coord object + if node.coord: + result['coord'] = str(node.coord) + else: + result['coord'] = None + + # Child attributes + for child_name, child in node.children(): + # Child strings are either simple (e.g. 'value') or arrays (e.g. 'block_items[1]') + match = RE_CHILD_ARRAY.match(child_name) + if match: + array_name, array_index = match.groups() + array_index = int(array_index) + # arrays come in order, so we verify and append. + result[array_name] = result.get(array_name, []) + if array_index != len(result[array_name]): + raise CJsonError('Internal ast error. Array {} out of order. ' + 'Expected index {}, got {}'.format( + array_name, len(result[array_name]), array_index)) + result[array_name].append(to_dict(child)) + else: + result[child_name] = to_dict(child) + + # Any child attributes that were missing need "None" values in the json. + for child_attr in child_attrs_of(klass): + if child_attr not in result: + result[child_attr] = None + + return result + + +def to_json(node, **kwargs): + """ Convert ast node to json string """ + return json.dumps(to_dict(node), **kwargs) + + +def file_to_dict(filename): + """ Load C file into dict representation of ast """ + ast = parse_file(filename, use_cpp=True) + return to_dict(ast) + + +def file_to_json(filename, **kwargs): + """ Load C file into json string representation of ast """ + ast = parse_file(filename, use_cpp=True) + return to_json(ast, **kwargs) + + +def _parse_coord(coord_str): + """ Parse coord string (file:line[:column]) into Coord object. """ + if coord_str is None: + return None + + vals = coord_str.split(':') + vals.extend([None] * 3) + filename, line, column = vals[:3] + return Coord(filename, line, column) + + +def _convert_to_obj(value): + """ + Convert an object in the dict representation into an object. + Note: Mutually recursive with from_dict. + + """ + value_type = type(value) + if value_type == dict: + return from_dict(value) + elif value_type == list: + return [_convert_to_obj(item) for item in value] + else: + # String + return value + + +def from_dict(node_dict): + """ Recursively build an ast from dict representation """ + class_name = node_dict.pop('_nodetype') + + klass = getattr(c_ast, class_name) + + # Create a new dict containing the key-value pairs which we can pass + # to node constructors. + objs = {} + for key, value in node_dict.items(): + if key == 'coord': + objs[key] = _parse_coord(value) + else: + objs[key] = _convert_to_obj(value) + + # Use keyword parameters, which works thanks to beautifully consistent + # ast Node initializers. + return klass(**objs) + + +def from_json(ast_json): + """ Build an ast from json string representation """ + return from_dict(json.loads(ast_json)) + + +#------------------------------------------------------------------------------ +if __name__ == "__main__": + if len(sys.argv) > 1: + # Some test code... + # Do trip from C -> ast -> dict -> ast -> json, then print. + ast_dict = file_to_dict(sys.argv[1]) + ast = from_dict(ast_dict) + print(to_json(ast, sort_keys=True, indent=4)) + else: + print("Please provide a filename as argument") diff --git a/examples/cdecl.py b/examples/cdecl.py new file mode 100644 index 0000000..351efc2 --- /dev/null +++ b/examples/cdecl.py @@ -0,0 +1,199 @@ +#----------------------------------------------------------------- +# pycparser: cdecl.py +# +# Example of the CDECL tool using pycparser. CDECL "explains" C type +# declarations in plain English. +# +# The AST generated by pycparser from the given declaration is traversed +# recursively to build the explanation. Note that the declaration must be a +# valid external declaration in C. As shown below, typedef can be optionally +# expanded. +# +# For example: +# +# c_decl = 'typedef int Node; const Node* (*ar)[10];' +# +# explain_c_declaration(c_decl) +# => ar is a pointer to array[10] of pointer to const Node +# +# struct and typedef can be optionally expanded: +# +# explain_c_declaration(c_decl, expand_typedef=True) +# => ar is a pointer to array[10] of pointer to const int +# +# c_decl = 'struct P {int x; int y;} p;' +# +# explain_c_declaration(c_decl) +# => p is a struct P +# +# explain_c_declaration(c_decl, expand_struct=True) +# => p is a struct P containing {x is a int, y is a int} +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +import copy +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import c_parser, c_ast + + +def explain_c_declaration(c_decl, expand_struct=False, expand_typedef=False): + """ Parses the declaration in c_decl and returns a text + explanation as a string. + + The last external node of the string is used, to allow earlier typedefs + for used types. + + expand_struct=True will spell out struct definitions recursively. + expand_typedef=True will expand typedef'd types. + """ + parser = c_parser.CParser() + + try: + node = parser.parse(c_decl, filename='<stdin>') + except c_parser.ParseError: + e = sys.exc_info()[1] + return "Parse error:" + str(e) + + if (not isinstance(node, c_ast.FileAST) or + not isinstance(node.ext[-1], c_ast.Decl) + ): + return "Not a valid declaration" + + try: + expanded = expand_struct_typedef(node.ext[-1], node, + expand_struct=expand_struct, + expand_typedef=expand_typedef) + except Exception as e: + return "Not a valid declaration: " + str(e) + + return _explain_decl_node(expanded) + + +def _explain_decl_node(decl_node): + """ Receives a c_ast.Decl note and returns its explanation in + English. + """ + storage = ' '.join(decl_node.storage) + ' ' if decl_node.storage else '' + + return (decl_node.name + + " is a " + + storage + + _explain_type(decl_node.type)) + + +def _explain_type(decl): + """ Recursively explains a type decl node + """ + typ = type(decl) + + if typ == c_ast.TypeDecl: + quals = ' '.join(decl.quals) + ' ' if decl.quals else '' + return quals + _explain_type(decl.type) + elif typ == c_ast.Typename or typ == c_ast.Decl: + return _explain_type(decl.type) + elif typ == c_ast.IdentifierType: + return ' '.join(decl.names) + elif typ == c_ast.PtrDecl: + quals = ' '.join(decl.quals) + ' ' if decl.quals else '' + return quals + 'pointer to ' + _explain_type(decl.type) + elif typ == c_ast.ArrayDecl: + arr = 'array' + if decl.dim: arr += '[%s]' % decl.dim.value + + return arr + " of " + _explain_type(decl.type) + + elif typ == c_ast.FuncDecl: + if decl.args: + params = [_explain_type(param) for param in decl.args.params] + args = ', '.join(params) + else: + args = '' + + return ('function(%s) returning ' % (args) + + _explain_type(decl.type)) + + elif typ == c_ast.Struct: + decls = [_explain_decl_node(mem_decl) for mem_decl in decl.decls] + members = ', '.join(decls) + + return ('struct%s ' % (' ' + decl.name if decl.name else '') + + ('containing {%s}' % members if members else '')) + + +def expand_struct_typedef(cdecl, file_ast, + expand_struct=False, + expand_typedef=False): + """Expand struct & typedef and return a new expanded node.""" + decl_copy = copy.deepcopy(cdecl) + _expand_in_place(decl_copy, file_ast, expand_struct, expand_typedef) + return decl_copy + + +def _expand_in_place(decl, file_ast, expand_struct=False, expand_typedef=False): + """Recursively expand struct & typedef in place, throw RuntimeError if + undeclared struct or typedef are used + """ + typ = type(decl) + + if typ in (c_ast.Decl, c_ast.TypeDecl, c_ast.PtrDecl, c_ast.ArrayDecl): + decl.type = _expand_in_place(decl.type, file_ast, expand_struct, + expand_typedef) + + elif typ == c_ast.Struct: + if not decl.decls: + struct = _find_struct(decl.name, file_ast) + if not struct: + raise RuntimeError('using undeclared struct %s' % decl.name) + decl.decls = struct.decls + + for i, mem_decl in enumerate(decl.decls): + decl.decls[i] = _expand_in_place(mem_decl, file_ast, expand_struct, + expand_typedef) + if not expand_struct: + decl.decls = [] + + elif (typ == c_ast.IdentifierType and + decl.names[0] not in ('int', 'char')): + typedef = _find_typedef(decl.names[0], file_ast) + if not typedef: + raise RuntimeError('using undeclared type %s' % decl.names[0]) + + if expand_typedef: + return typedef.type + + return decl + + +def _find_struct(name, file_ast): + """Receives a struct name and return declared struct object in file_ast + """ + for node in file_ast.ext: + if (type(node) == c_ast.Decl and + type(node.type) == c_ast.Struct and + node.type.name == name): + return node.type + + +def _find_typedef(name, file_ast): + """Receives a type name and return typedef object in file_ast + """ + for node in file_ast.ext: + if type(node) == c_ast.Typedef and node.name == name: + return node + + +if __name__ == "__main__": + if len(sys.argv) > 1: + c_decl = sys.argv[1] + else: + c_decl = "char *(*(**foo[][8])())[];" + + print("Explaining the declaration: " + c_decl + "\n") + print(explain_c_declaration(c_decl) + "\n") diff --git a/examples/dump_ast.py b/examples/dump_ast.py new file mode 100644 index 0000000..2cff874 --- /dev/null +++ b/examples/dump_ast.py @@ -0,0 +1,25 @@ +#----------------------------------------------------------------- +# pycparser: dump_ast.py +# +# Basic example of parsing a file and dumping its parsed AST. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import argparse +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +sys.path.extend(['.', '..']) + +from pycparser import c_parser, c_ast, parse_file + +if __name__ == "__main__": + argparser = argparse.ArgumentParser('Dump AST') + argparser.add_argument('filename', help='name of file to parse') + args = argparser.parse_args() + + ast = parse_file(args.filename, use_cpp=False) + ast.show() diff --git a/examples/explore_ast.py b/examples/explore_ast.py new file mode 100644 index 0000000..1f6e0ae --- /dev/null +++ b/examples/explore_ast.py @@ -0,0 +1,159 @@ +#----------------------------------------------------------------- +# pycparser: explore_ast.py +# +# This example demonstrates how to "explore" the AST created by +# pycparser to understand its structure. The AST is a n-nary tree +# of nodes, each node having several children, each with a name. +# Just read the code, and let the comments guide you. The lines +# beginning with #~ can be uncommented to print out useful +# information from the AST. +# It helps to have the pycparser/_c_ast.cfg file in front of you. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import c_parser, c_ast + +# This is some C source to parse. Note that pycparser must begin +# at the top level of the C file, i.e. with either declarations +# or function definitions (this is called "external declarations" +# in C grammar lingo) +# +# Also, a C parser must have all the types declared in order to +# build the correct AST. It doesn't matter what they're declared +# to, so I've inserted the dummy typedef in the code to let the +# parser know Hash and Node are types. You don't need to do it +# when parsing real, correct C code. + +text = r""" + typedef int Node, Hash; + + void HashPrint(Hash* hash, void (*PrintFunc)(char*, char*)) + { + unsigned int i; + + if (hash == NULL || hash->heads == NULL) + return; + + for (i = 0; i < hash->table_size; ++i) + { + Node* temp = hash->heads[i]; + + while (temp != NULL) + { + PrintFunc(temp->entry->key, temp->entry->value); + temp = temp->next; + } + } + } +""" + +# Create the parser and ask to parse the text. parse() will throw +# a ParseError if there's an error in the code +# +parser = c_parser.CParser() +ast = parser.parse(text, filename='<none>') + +# Uncomment the following line to see the AST in a nice, human +# readable way. show() is the most useful tool in exploring ASTs +# created by pycparser. See the c_ast.py file for the options you +# can pass it. + +#ast.show(showcoord=True) + +# OK, we've seen that the top node is FileAST. This is always the +# top node of the AST. Its children are "external declarations", +# and are stored in a list called ext[] (see _c_ast.cfg for the +# names and types of Nodes and their children). +# As you see from the printout, our AST has two Typedef children +# and one FuncDef child. +# Let's explore FuncDef more closely. As I've mentioned, the list +# ext[] holds the children of FileAST. Since the function +# definition is the third child, it's ext[2]. Uncomment the +# following line to show it: + +#ast.ext[2].show() + +# A FuncDef consists of a declaration, a list of parameter +# declarations (for K&R style function definitions), and a body. +# First, let's examine the declaration. + +function_decl = ast.ext[2].decl + +# function_decl, like any other declaration, is a Decl. Its type child +# is a FuncDecl, which has a return type and arguments stored in a +# ParamList node + +#function_decl.type.show() +#function_decl.type.args.show() + +# The following displays the name and type of each argument: + +#for param_decl in function_decl.type.args.params: + #print('Arg name: %s' % param_decl.name) + #print('Type:') + #param_decl.type.show(offset=6) + +# The body is of FuncDef is a Compound, which is a placeholder for a block +# surrounded by {} (You should be reading _c_ast.cfg parallel to this +# explanation and seeing these things with your own eyes). +# Let's see the block's declarations: + +function_body = ast.ext[2].body + +# The following displays the declarations and statements in the function +# body + +#for decl in function_body.block_items: + #decl.show() + +# We can see a single variable declaration, i, declared to be a simple type +# declaration of type 'unsigned int', followed by statements. + +# block_items is a list, so the third element is the For statement: + +for_stmt = function_body.block_items[2] +#for_stmt.show() + +# As you can see in _c_ast.cfg, For's children are 'init, cond, +# next' for the respective parts of the 'for' loop specifier, +# and stmt, which is either a single stmt or a Compound if there's +# a block. +# +# Let's dig deeper, to the while statement inside the for loop: + +while_stmt = for_stmt.stmt.block_items[1] +#while_stmt.show() + +# While is simpler, it only has a condition node and a stmt node. +# The condition: + +while_cond = while_stmt.cond +#while_cond.show() + +# Note that it's a BinaryOp node - the basic constituent of +# expressions in our AST. BinaryOp is the expression tree, with +# left and right nodes as children. It also has the op attribute, +# which is just the string representation of the operator. + +#print(while_cond.op) +#while_cond.left.show() +#while_cond.right.show() + + +# That's it for the example. I hope you now see how easy it is to explore the +# AST created by pycparser. Although on the surface it is quite complex and has +# a lot of node types, this is the inherent complexity of the C language every +# parser/compiler designer has to cope with. +# Using the tools provided by the c_ast package it's easy to explore the +# structure of AST nodes and write code that processes them. +# Specifically, see the cdecl.py example for a non-trivial demonstration of what +# you can do by recursively going through the AST. diff --git a/examples/func_calls.py b/examples/func_calls.py new file mode 100644 index 0000000..ec31fe5 --- /dev/null +++ b/examples/func_calls.py @@ -0,0 +1,46 @@ +#----------------------------------------------------------------- +# pycparser: func_calls.py +# +# Using pycparser for printing out all the calls of some function +# in a C file. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +sys.path.extend(['.', '..']) + +from pycparser import c_parser, c_ast, parse_file + + +# A visitor with some state information (the funcname it's +# looking for) +# +class FuncCallVisitor(c_ast.NodeVisitor): + def __init__(self, funcname): + self.funcname = funcname + + def visit_FuncCall(self, node): + if node.name.name == self.funcname: + print('%s called at %s' % (self.funcname, node.name.coord)) + + +def show_func_calls(filename, funcname): + ast = parse_file(filename, use_cpp=True) + v = FuncCallVisitor(funcname) + v.visit(ast) + + +if __name__ == "__main__": + if len(sys.argv) > 2: + filename = sys.argv[1] + func = sys.argv[2] + else: + filename = 'examples/c_files/hash.c' + func = 'malloc' + + show_func_calls(filename, func) diff --git a/examples/func_defs.py b/examples/func_defs.py new file mode 100644 index 0000000..8fe9889 --- /dev/null +++ b/examples/func_defs.py @@ -0,0 +1,46 @@ +#----------------------------------------------------------------- +# pycparser: func_defs.py +# +# Using pycparser for printing out all the functions defined in a +# C file. +# +# This is a simple example of traversing the AST generated by +# pycparser. Call it from the root directory of pycparser. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +sys.path.extend(['.', '..']) + +from pycparser import c_parser, c_ast, parse_file + + +# A simple visitor for FuncDef nodes that prints the names and +# locations of function definitions. +class FuncDefVisitor(c_ast.NodeVisitor): + def visit_FuncDef(self, node): + print('%s at %s' % (node.decl.name, node.decl.coord)) + + +def show_func_defs(filename): + # Note that cpp is used. Provide a path to your own cpp or + # make sure one exists in PATH. + ast = parse_file(filename, use_cpp=True, + cpp_args=r'-Iutils/fake_libc_include') + + v = FuncDefVisitor() + v.visit(ast) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + filename = sys.argv[1] + else: + filename = 'examples/c_files/memmgr.c' + + show_func_defs(filename) diff --git a/examples/rewrite_ast.py b/examples/rewrite_ast.py new file mode 100644 index 0000000..2c42f99 --- /dev/null +++ b/examples/rewrite_ast.py @@ -0,0 +1,31 @@ +#----------------------------------------------------------------- +# pycparser: rewrite_ast.py +# +# Tiny example of rewriting a AST node +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import sys + +from pycparser import c_parser + +text = r""" +void func(void) +{ + x = 1; +} +""" + +parser = c_parser.CParser() +ast = parser.parse(text) +print("Before:") +ast.show(offset=2) + +assign = ast.ext[0].body.block_items[0] +assign.lvalue.name = "y" +assign.rvalue.value = 2 + +print("After:") +ast.show(offset=2) diff --git a/examples/serialize_ast.py b/examples/serialize_ast.py new file mode 100644 index 0000000..e0f8aa3 --- /dev/null +++ b/examples/serialize_ast.py @@ -0,0 +1,36 @@ +#----------------------------------------------------------------- +# pycparser: serialize_ast.py +# +# Simple example of serializing AST +# +# Hart Chu [https://github.com/CtheSky] +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +from __future__ import print_function +import pickle + +from pycparser import c_parser + +text = r""" +void func(void) +{ + x = 1; +} +""" + +parser = c_parser.CParser() +ast = parser.parse(text) + +# Since AST nodes use __slots__ for faster attribute access and +# space saving, it needs Pickle's protocol version >= 2. +# The default version is 3 for python 3.x and 1 for python 2.7. +# You can always select the highest available protocol with the -1 argument. + +with open('ast', 'wb') as f: + pickle.dump(ast, f, protocol=-1) + +# Deserialize. +with open('ast', 'rb') as f: + ast = pickle.load(f) + ast.show() diff --git a/examples/using_cpp_libc.py b/examples/using_cpp_libc.py new file mode 100644 index 0000000..e930f5b --- /dev/null +++ b/examples/using_cpp_libc.py @@ -0,0 +1,30 @@ +#----------------------------------------------------------------- +# pycparser: using_cpp_libc.py +# +# Shows how to use the provided 'cpp' (on Windows, substitute for +# the 'real' cpp if you're on Linux/Unix) and "fake" libc includes +# to parse a file that includes standard C headers. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import parse_file + + +if __name__ == "__main__": + if len(sys.argv) > 1: + filename = sys.argv[1] + else: + filename = 'examples/c_files/year.c' + + ast = parse_file(filename, use_cpp=True, + cpp_path='cpp', + cpp_args=r'-Iutils/fake_libc_include') + ast.show() diff --git a/examples/using_gcc_E_libc.py b/examples/using_gcc_E_libc.py new file mode 100644 index 0000000..bba4d3d --- /dev/null +++ b/examples/using_gcc_E_libc.py @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------------- +# pycparser: using_gcc_E_libc.py +# +# Similar to the using_cpp_libc.py example, but uses 'gcc -E' instead +# of 'cpp'. The same can be achieved with Clang instead of gcc. If you have +# Clang installed, simply replace 'gcc' with 'clang' here. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------- +import sys + +# This is not required if you've installed pycparser into +# your site-packages/ with setup.py +# +sys.path.extend(['.', '..']) + +from pycparser import parse_file + + +if __name__ == "__main__": + if len(sys.argv) > 1: + filename = sys.argv[1] + else: + filename = 'examples/c_files/year.c' + + ast = parse_file(filename, use_cpp=True, + cpp_path='gcc', + cpp_args=['-E', r'-Iutils/fake_libc_include']) + ast.show() diff --git a/pycparser/Android.bp b/pycparser/Android.bp new file mode 100644 index 0000000..4fee1fb --- /dev/null +++ b/pycparser/Android.bp @@ -0,0 +1,33 @@ +// Copyright 2019 Google Inc. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +python_library { + name: "py-pycparser", + host_supported: true, + srcs: [ + "*.py", + "ply/*.py", + ], + data: [ + "_c_ast.cfg", + ], + version: { + py2: { + enabled: true, + }, + py3: { + enabled: true, + }, + }, + pkg_path: "pycparser", +} diff --git a/pycparser/__init__.py b/pycparser/__init__.py new file mode 100644 index 0000000..b67389f --- /dev/null +++ b/pycparser/__init__.py @@ -0,0 +1,90 @@ +#----------------------------------------------------------------- +# pycparser: __init__.py +# +# This package file exports some convenience functions for +# interacting with pycparser +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +__all__ = ['c_lexer', 'c_parser', 'c_ast'] +__version__ = '2.19' + +import io +from subprocess import check_output +from .c_parser import CParser + + +def preprocess_file(filename, cpp_path='cpp', cpp_args=''): + """ Preprocess a file using cpp. + + filename: + Name of the file you want to preprocess. + + cpp_path: + cpp_args: + Refer to the documentation of parse_file for the meaning of these + arguments. + + When successful, returns the preprocessed file's contents. + Errors from cpp will be printed out. + """ + path_list = [cpp_path] + if isinstance(cpp_args, list): + path_list += cpp_args + elif cpp_args != '': + path_list += [cpp_args] + path_list += [filename] + + try: + # Note the use of universal_newlines to treat all newlines + # as \n for Python's purpose + text = check_output(path_list, universal_newlines=True) + except OSError as e: + raise RuntimeError("Unable to invoke 'cpp'. " + + 'Make sure its path was passed correctly\n' + + ('Original error: %s' % e)) + + return text + + +def parse_file(filename, use_cpp=False, cpp_path='cpp', cpp_args='', + parser=None): + """ Parse a C file using pycparser. + + filename: + Name of the file you want to parse. + + use_cpp: + Set to True if you want to execute the C pre-processor + on the file prior to parsing it. + + cpp_path: + If use_cpp is True, this is the path to 'cpp' on your + system. If no path is provided, it attempts to just + execute 'cpp', so it must be in your PATH. + + cpp_args: + If use_cpp is True, set this to the command line arguments strings + to cpp. Be careful with quotes - it's best to pass a raw string + (r'') here. For example: + r'-I../utils/fake_libc_include' + If several arguments are required, pass a list of strings. + + parser: + Optional parser object to be used instead of the default CParser + + When successful, an AST is returned. ParseError can be + thrown if the file doesn't parse successfully. + + Errors from cpp will be printed out. + """ + if use_cpp: + text = preprocess_file(filename, cpp_path, cpp_args) + else: + with io.open(filename) as f: + text = f.read() + + if parser is None: + parser = CParser() + return parser.parse(text, filename) diff --git a/pycparser/_ast_gen.py b/pycparser/_ast_gen.py new file mode 100644 index 0000000..5ec2d3d --- /dev/null +++ b/pycparser/_ast_gen.py @@ -0,0 +1,338 @@ +#----------------------------------------------------------------- +# _ast_gen.py +# +# Generates the AST Node classes from a specification given in +# a configuration file +# +# The design of this module was inspired by astgen.py from the +# Python 2.5 code-base. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- +import pprint +from string import Template + + +class ASTCodeGenerator(object): + def __init__(self, cfg_filename='_c_ast.cfg'): + """ Initialize the code generator from a configuration + file. + """ + self.cfg_filename = cfg_filename + self.node_cfg = [NodeCfg(name, contents) + for (name, contents) in self.parse_cfgfile(cfg_filename)] + + def generate(self, file=None): + """ Generates the code into file, an open file buffer. + """ + src = Template(_PROLOGUE_COMMENT).substitute( + cfg_filename=self.cfg_filename) + + src += _PROLOGUE_CODE + for node_cfg in self.node_cfg: + src += node_cfg.generate_source() + '\n\n' + + file.write(src) + + def parse_cfgfile(self, filename): + """ Parse the configuration file and yield pairs of + (name, contents) for each node. + """ + with open(filename, "r") as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + colon_i = line.find(':') + lbracket_i = line.find('[') + rbracket_i = line.find(']') + if colon_i < 1 or lbracket_i <= colon_i or rbracket_i <= lbracket_i: + raise RuntimeError("Invalid line in %s:\n%s\n" % (filename, line)) + + name = line[:colon_i] + val = line[lbracket_i + 1:rbracket_i] + vallist = [v.strip() for v in val.split(',')] if val else [] + yield name, vallist + + +class NodeCfg(object): + """ Node configuration. + + name: node name + contents: a list of contents - attributes and child nodes + See comment at the top of the configuration file for details. + """ + + def __init__(self, name, contents): + self.name = name + self.all_entries = [] + self.attr = [] + self.child = [] + self.seq_child = [] + + for entry in contents: + clean_entry = entry.rstrip('*') + self.all_entries.append(clean_entry) + + if entry.endswith('**'): + self.seq_child.append(clean_entry) + elif entry.endswith('*'): + self.child.append(clean_entry) + else: + self.attr.append(entry) + + def generate_source(self): + src = self._gen_init() + src += '\n' + self._gen_children() + src += '\n' + self._gen_iter() + + src += '\n' + self._gen_attr_names() + return src + + def _gen_init(self): + src = "class %s(Node):\n" % self.name + + if self.all_entries: + args = ', '.join(self.all_entries) + slots = ', '.join("'{0}'".format(e) for e in self.all_entries) + slots += ", 'coord', '__weakref__'" + arglist = '(self, %s, coord=None)' % args + else: + slots = "'coord', '__weakref__'" + arglist = '(self, coord=None)' + + src += " __slots__ = (%s)\n" % slots + src += " def __init__%s:\n" % arglist + + for name in self.all_entries + ['coord']: + src += " self.%s = %s\n" % (name, name) + + return src + + def _gen_children(self): + src = ' def children(self):\n' + + if self.all_entries: + src += ' nodelist = []\n' + + for child in self.child: + src += ( + ' if self.%(child)s is not None:' + + ' nodelist.append(("%(child)s", self.%(child)s))\n') % ( + dict(child=child)) + + for seq_child in self.seq_child: + src += ( + ' for i, child in enumerate(self.%(child)s or []):\n' + ' nodelist.append(("%(child)s[%%d]" %% i, child))\n') % ( + dict(child=seq_child)) + + src += ' return tuple(nodelist)\n' + else: + src += ' return ()\n' + + return src + + def _gen_iter(self): + src = ' def __iter__(self):\n' + + if self.all_entries: + for child in self.child: + src += ( + ' if self.%(child)s is not None:\n' + + ' yield self.%(child)s\n') % (dict(child=child)) + + for seq_child in self.seq_child: + src += ( + ' for child in (self.%(child)s or []):\n' + ' yield child\n') % (dict(child=seq_child)) + + if not (self.child or self.seq_child): + # Empty generator + src += ( + ' return\n' + + ' yield\n') + else: + # Empty generator + src += ( + ' return\n' + + ' yield\n') + + return src + + def _gen_attr_names(self): + src = " attr_names = (" + ''.join("%r, " % nm for nm in self.attr) + ')' + return src + + +_PROLOGUE_COMMENT = \ +r'''#----------------------------------------------------------------- +# ** ATTENTION ** +# This code was automatically generated from the file: +# $cfg_filename +# +# Do not modify it directly. Modify the configuration file and +# run the generator again. +# ** ** *** ** ** +# +# pycparser: c_ast.py +# +# AST Node classes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +''' + +_PROLOGUE_CODE = r''' +import sys + +def _repr(obj): + """ + Get the representation of an object, with dedicated pprint-like format for lists. + """ + if isinstance(obj, list): + return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]' + else: + return repr(obj) + +class Node(object): + __slots__ = () + """ Abstract base class for AST nodes. + """ + def __repr__(self): + """ Generates a python representation of the current node + """ + result = self.__class__.__name__ + '(' + + indent = '' + separator = '' + for name in self.__slots__[:-2]: + result += separator + result += indent + result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__))))) + + separator = ',' + indent = '\n ' + (' ' * len(self.__class__.__name__)) + + result += indent + ')' + + return result + + def children(self): + """ A sequence of all children that are Nodes + """ + pass + + def show(self, buf=sys.stdout, offset=0, attrnames=False, nodenames=False, showcoord=False, _my_node_name=None): + """ Pretty print the Node and all its attributes and + children (recursively) to a buffer. + + buf: + Open IO buffer into which the Node is printed. + + offset: + Initial offset (amount of leading spaces) + + attrnames: + True if you want to see the attribute names in + name=value pairs. False to only see the values. + + nodenames: + True if you want to see the actual node names + within their parents. + + showcoord: + Do you want the coordinates of each Node to be + displayed. + """ + lead = ' ' * offset + if nodenames and _my_node_name is not None: + buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ') + else: + buf.write(lead + self.__class__.__name__+ ': ') + + if self.attr_names: + if attrnames: + nvlist = [(n, getattr(self,n)) for n in self.attr_names] + attrstr = ', '.join('%s=%s' % nv for nv in nvlist) + else: + vlist = [getattr(self, n) for n in self.attr_names] + attrstr = ', '.join('%s' % v for v in vlist) + buf.write(attrstr) + + if showcoord: + buf.write(' (at %s)' % self.coord) + buf.write('\n') + + for (child_name, child) in self.children(): + child.show( + buf, + offset=offset + 2, + attrnames=attrnames, + nodenames=nodenames, + showcoord=showcoord, + _my_node_name=child_name) + + +class NodeVisitor(object): + """ A base NodeVisitor class for visiting c_ast nodes. + Subclass it and define your own visit_XXX methods, where + XXX is the class name you want to visit with these + methods. + + For example: + + class ConstantVisitor(NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + Creates a list of values of all the constant nodes + encountered below the given node. To use it: + + cv = ConstantVisitor() + cv.visit(node) + + Notes: + + * generic_visit() will be called for AST nodes for which + no visit_XXX method was defined. + * The children of nodes for which a visit_XXX was + defined will not be visited - if you need this, call + generic_visit() on the node. + You can use: + NodeVisitor.generic_visit(self, node) + * Modeled after Python's own AST visiting facilities + (the ast module of Python 3.0) + """ + + _method_cache = None + + def visit(self, node): + """ Visit a node. + """ + + if self._method_cache is None: + self._method_cache = {} + + visitor = self._method_cache.get(node.__class__.__name__, None) + if visitor is None: + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + self._method_cache[node.__class__.__name__] = visitor + + return visitor(node) + + def generic_visit(self, node): + """ Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + for c in node: + self.visit(c) + +''' diff --git a/pycparser/_build_tables.py b/pycparser/_build_tables.py new file mode 100644 index 0000000..94a3891 --- /dev/null +++ b/pycparser/_build_tables.py @@ -0,0 +1,33 @@ +#----------------------------------------------------------------- +# pycparser: _build_tables.py +# +# A dummy for generating the lexing/parsing tables and and +# compiling them into .pyc for faster execution in optimized mode. +# Also generates AST code from the configuration file. +# Should be called from the pycparser directory. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +# Generate c_ast.py +from _ast_gen import ASTCodeGenerator +ast_gen = ASTCodeGenerator('_c_ast.cfg') +ast_gen.generate(open('c_ast.py', 'w')) + +import sys +sys.path[0:0] = ['.', '..'] +from pycparser import c_parser + +# Generates the tables +# +c_parser.CParser( + lex_optimize=True, + yacc_debug=False, + yacc_optimize=True) + +# Load to compile into .pyc +# +import lextab +import yacctab +import c_ast diff --git a/pycparser/_c_ast.cfg b/pycparser/_c_ast.cfg new file mode 100644 index 0000000..b93d50b --- /dev/null +++ b/pycparser/_c_ast.cfg @@ -0,0 +1,191 @@ +#----------------------------------------------------------------- +# pycparser: _c_ast.cfg +# +# Defines the AST Node classes used in pycparser. +# +# Each entry is a Node sub-class name, listing the attributes +# and child nodes of the class: +# <name>* - a child node +# <name>** - a sequence of child nodes +# <name> - an attribute +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +# ArrayDecl is a nested declaration of an array with the given type. +# dim: the dimension (for example, constant 42) +# dim_quals: list of dimension qualifiers, to support C99's allowing 'const' +# and 'static' within the array dimension in function declarations. +ArrayDecl: [type*, dim*, dim_quals] + +ArrayRef: [name*, subscript*] + +# op: =, +=, /= etc. +# +Assignment: [op, lvalue*, rvalue*] + +BinaryOp: [op, left*, right*] + +Break: [] + +Case: [expr*, stmts**] + +Cast: [to_type*, expr*] + +# Compound statement in C99 is a list of block items (declarations or +# statements). +# +Compound: [block_items**] + +# Compound literal (anonymous aggregate) for C99. +# (type-name) {initializer_list} +# type: the typename +# init: InitList for the initializer list +# +CompoundLiteral: [type*, init*] + +# type: int, char, float, etc. see CLexer for constant token types +# +Constant: [type, value] + +Continue: [] + +# name: the variable being declared +# quals: list of qualifiers (const, volatile) +# funcspec: list function specifiers (i.e. inline in C99) +# storage: list of storage specifiers (extern, register, etc.) +# type: declaration type (probably nested with all the modifiers) +# init: initialization value, or None +# bitsize: bit field size, or None +# +Decl: [name, quals, storage, funcspec, type*, init*, bitsize*] + +DeclList: [decls**] + +Default: [stmts**] + +DoWhile: [cond*, stmt*] + +# Represents the ellipsis (...) parameter in a function +# declaration +# +EllipsisParam: [] + +# An empty statement (a semicolon ';' on its own) +# +EmptyStatement: [] + +# Enumeration type specifier +# name: an optional ID +# values: an EnumeratorList +# +Enum: [name, values*] + +# A name/value pair for enumeration values +# +Enumerator: [name, value*] + +# A list of enumerators +# +EnumeratorList: [enumerators**] + +# A list of expressions separated by the comma operator. +# +ExprList: [exprs**] + +# This is the top of the AST, representing a single C file (a +# translation unit in K&R jargon). It contains a list of +# "external-declaration"s, which is either declarations (Decl), +# Typedef or function definitions (FuncDef). +# +FileAST: [ext**] + +# for (init; cond; next) stmt +# +For: [init*, cond*, next*, stmt*] + +# name: Id +# args: ExprList +# +FuncCall: [name*, args*] + +# type <decl>(args) +# +FuncDecl: [args*, type*] + +# Function definition: a declarator for the function name and +# a body, which is a compound statement. +# There's an optional list of parameter declarations for old +# K&R-style definitions +# +FuncDef: [decl*, param_decls**, body*] + +Goto: [name] + +ID: [name] + +# Holder for types that are a simple identifier (e.g. the built +# ins void, char etc. and typedef-defined types) +# +IdentifierType: [names] + +If: [cond*, iftrue*, iffalse*] + +# An initialization list used for compound literals. +# +InitList: [exprs**] + +Label: [name, stmt*] + +# A named initializer for C99. +# The name of a NamedInitializer is a sequence of Nodes, because +# names can be hierarchical and contain constant expressions. +# +NamedInitializer: [name**, expr*] + +# a list of comma separated function parameter declarations +# +ParamList: [params**] + +PtrDecl: [quals, type*] + +Return: [expr*] + +# name: struct tag name +# decls: declaration of members +# +Struct: [name, decls**] + +# type: . or -> +# name.field or name->field +# +StructRef: [name*, type, field*] + +Switch: [cond*, stmt*] + +# cond ? iftrue : iffalse +# +TernaryOp: [cond*, iftrue*, iffalse*] + +# A base type declaration +# +TypeDecl: [declname, quals, type*] + +# A typedef declaration. +# Very similar to Decl, but without some attributes +# +Typedef: [name, quals, storage, type*] + +Typename: [name, quals, type*] + +UnaryOp: [op, expr*] + +# name: union tag name +# decls: declaration of members +# +Union: [name, decls**] + +While: [cond*, stmt*] + +Pragma: [string] diff --git a/pycparser/ast_transforms.py b/pycparser/ast_transforms.py new file mode 100644 index 0000000..ba50966 --- /dev/null +++ b/pycparser/ast_transforms.py @@ -0,0 +1,105 @@ +#------------------------------------------------------------------------------ +# pycparser: ast_transforms.py +# +# Some utilities used by the parser to create a friendlier AST. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------ + +from . import c_ast + + +def fix_switch_cases(switch_node): + """ The 'case' statements in a 'switch' come out of parsing with one + child node, so subsequent statements are just tucked to the parent + Compound. Additionally, consecutive (fall-through) case statements + come out messy. This is a peculiarity of the C grammar. The following: + + switch (myvar) { + case 10: + k = 10; + p = k + 1; + return 10; + case 20: + case 30: + return 20; + default: + break; + } + + Creates this tree (pseudo-dump): + + Switch + ID: myvar + Compound: + Case 10: + k = 10 + p = k + 1 + return 10 + Case 20: + Case 30: + return 20 + Default: + break + + The goal of this transform is to fix this mess, turning it into the + following: + + Switch + ID: myvar + Compound: + Case 10: + k = 10 + p = k + 1 + return 10 + Case 20: + Case 30: + return 20 + Default: + break + + A fixed AST node is returned. The argument may be modified. + """ + assert isinstance(switch_node, c_ast.Switch) + if not isinstance(switch_node.stmt, c_ast.Compound): + return switch_node + + # The new Compound child for the Switch, which will collect children in the + # correct order + new_compound = c_ast.Compound([], switch_node.stmt.coord) + + # The last Case/Default node + last_case = None + + # Goes over the children of the Compound below the Switch, adding them + # either directly below new_compound or below the last Case as appropriate + for child in switch_node.stmt.block_items: + if isinstance(child, (c_ast.Case, c_ast.Default)): + # If it's a Case/Default: + # 1. Add it to the Compound and mark as "last case" + # 2. If its immediate child is also a Case or Default, promote it + # to a sibling. + new_compound.block_items.append(child) + _extract_nested_case(child, new_compound.block_items) + last_case = new_compound.block_items[-1] + else: + # Other statements are added as children to the last case, if it + # exists. + if last_case is None: + new_compound.block_items.append(child) + else: + last_case.stmts.append(child) + + switch_node.stmt = new_compound + return switch_node + + +def _extract_nested_case(case_node, stmts_list): + """ Recursively extract consecutive Case statements that are made nested + by the parser and add them to the stmts_list. + """ + if isinstance(case_node.stmts[0], (c_ast.Case, c_ast.Default)): + stmts_list.append(case_node.stmts.pop()) + _extract_nested_case(stmts_list[-1], stmts_list) + diff --git a/pycparser/c_ast.py b/pycparser/c_ast.py new file mode 100644 index 0000000..b7bbbee --- /dev/null +++ b/pycparser/c_ast.py @@ -0,0 +1,1084 @@ +#----------------------------------------------------------------- +# ** ATTENTION ** +# This code was automatically generated from the file: +# _c_ast.cfg +# +# Do not modify it directly. Modify the configuration file and +# run the generator again. +# ** ** *** ** ** +# +# pycparser: c_ast.py +# +# AST Node classes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + + +import sys + +def _repr(obj): + """ + Get the representation of an object, with dedicated pprint-like format for lists. + """ + if isinstance(obj, list): + return '[' + (',\n '.join((_repr(e).replace('\n', '\n ') for e in obj))) + '\n]' + else: + return repr(obj) + +class Node(object): + __slots__ = () + """ Abstract base class for AST nodes. + """ + def __repr__(self): + """ Generates a python representation of the current node + """ + result = self.__class__.__name__ + '(' + + indent = '' + separator = '' + for name in self.__slots__[:-2]: + result += separator + result += indent + result += name + '=' + (_repr(getattr(self, name)).replace('\n', '\n ' + (' ' * (len(name) + len(self.__class__.__name__))))) + + separator = ',' + indent = '\n ' + (' ' * len(self.__class__.__name__)) + + result += indent + ')' + + return result + + def children(self): + """ A sequence of all children that are Nodes + """ + pass + + def show(self, buf=sys.stdout, offset=0, attrnames=False, nodenames=False, showcoord=False, _my_node_name=None): + """ Pretty print the Node and all its attributes and + children (recursively) to a buffer. + + buf: + Open IO buffer into which the Node is printed. + + offset: + Initial offset (amount of leading spaces) + + attrnames: + True if you want to see the attribute names in + name=value pairs. False to only see the values. + + nodenames: + True if you want to see the actual node names + within their parents. + + showcoord: + Do you want the coordinates of each Node to be + displayed. + """ + lead = ' ' * offset + if nodenames and _my_node_name is not None: + buf.write(lead + self.__class__.__name__+ ' <' + _my_node_name + '>: ') + else: + buf.write(lead + self.__class__.__name__+ ': ') + + if self.attr_names: + if attrnames: + nvlist = [(n, getattr(self,n)) for n in self.attr_names] + attrstr = ', '.join('%s=%s' % nv for nv in nvlist) + else: + vlist = [getattr(self, n) for n in self.attr_names] + attrstr = ', '.join('%s' % v for v in vlist) + buf.write(attrstr) + + if showcoord: + buf.write(' (at %s)' % self.coord) + buf.write('\n') + + for (child_name, child) in self.children(): + child.show( + buf, + offset=offset + 2, + attrnames=attrnames, + nodenames=nodenames, + showcoord=showcoord, + _my_node_name=child_name) + + +class NodeVisitor(object): + """ A base NodeVisitor class for visiting c_ast nodes. + Subclass it and define your own visit_XXX methods, where + XXX is the class name you want to visit with these + methods. + + For example: + + class ConstantVisitor(NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + Creates a list of values of all the constant nodes + encountered below the given node. To use it: + + cv = ConstantVisitor() + cv.visit(node) + + Notes: + + * generic_visit() will be called for AST nodes for which + no visit_XXX method was defined. + * The children of nodes for which a visit_XXX was + defined will not be visited - if you need this, call + generic_visit() on the node. + You can use: + NodeVisitor.generic_visit(self, node) + * Modeled after Python's own AST visiting facilities + (the ast module of Python 3.0) + """ + + _method_cache = None + + def visit(self, node): + """ Visit a node. + """ + + if self._method_cache is None: + self._method_cache = {} + + visitor = self._method_cache.get(node.__class__.__name__, None) + if visitor is None: + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + self._method_cache[node.__class__.__name__] = visitor + + return visitor(node) + + def generic_visit(self, node): + """ Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + for c in node: + self.visit(c) + +class ArrayDecl(Node): + __slots__ = ('type', 'dim', 'dim_quals', 'coord', '__weakref__') + def __init__(self, type, dim, dim_quals, coord=None): + self.type = type + self.dim = dim + self.dim_quals = dim_quals + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + if self.dim is not None: nodelist.append(("dim", self.dim)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.dim is not None: + yield self.dim + + attr_names = ('dim_quals', ) + +class ArrayRef(Node): + __slots__ = ('name', 'subscript', 'coord', '__weakref__') + def __init__(self, name, subscript, coord=None): + self.name = name + self.subscript = subscript + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: nodelist.append(("name", self.name)) + if self.subscript is not None: nodelist.append(("subscript", self.subscript)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.subscript is not None: + yield self.subscript + + attr_names = () + +class Assignment(Node): + __slots__ = ('op', 'lvalue', 'rvalue', 'coord', '__weakref__') + def __init__(self, op, lvalue, rvalue, coord=None): + self.op = op + self.lvalue = lvalue + self.rvalue = rvalue + self.coord = coord + + def children(self): + nodelist = [] + if self.lvalue is not None: nodelist.append(("lvalue", self.lvalue)) + if self.rvalue is not None: nodelist.append(("rvalue", self.rvalue)) + return tuple(nodelist) + + def __iter__(self): + if self.lvalue is not None: + yield self.lvalue + if self.rvalue is not None: + yield self.rvalue + + attr_names = ('op', ) + +class BinaryOp(Node): + __slots__ = ('op', 'left', 'right', 'coord', '__weakref__') + def __init__(self, op, left, right, coord=None): + self.op = op + self.left = left + self.right = right + self.coord = coord + + def children(self): + nodelist = [] + if self.left is not None: nodelist.append(("left", self.left)) + if self.right is not None: nodelist.append(("right", self.right)) + return tuple(nodelist) + + def __iter__(self): + if self.left is not None: + yield self.left + if self.right is not None: + yield self.right + + attr_names = ('op', ) + +class Break(Node): + __slots__ = ('coord', '__weakref__') + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + +class Case(Node): + __slots__ = ('expr', 'stmts', 'coord', '__weakref__') + def __init__(self, expr, stmts, coord=None): + self.expr = expr + self.stmts = stmts + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: nodelist.append(("expr", self.expr)) + for i, child in enumerate(self.stmts or []): + nodelist.append(("stmts[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + for child in (self.stmts or []): + yield child + + attr_names = () + +class Cast(Node): + __slots__ = ('to_type', 'expr', 'coord', '__weakref__') + def __init__(self, to_type, expr, coord=None): + self.to_type = to_type + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.to_type is not None: nodelist.append(("to_type", self.to_type)) + if self.expr is not None: nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.to_type is not None: + yield self.to_type + if self.expr is not None: + yield self.expr + + attr_names = () + +class Compound(Node): + __slots__ = ('block_items', 'coord', '__weakref__') + def __init__(self, block_items, coord=None): + self.block_items = block_items + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.block_items or []): + nodelist.append(("block_items[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.block_items or []): + yield child + + attr_names = () + +class CompoundLiteral(Node): + __slots__ = ('type', 'init', 'coord', '__weakref__') + def __init__(self, type, init, coord=None): + self.type = type + self.init = init + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + if self.init is not None: nodelist.append(("init", self.init)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.init is not None: + yield self.init + + attr_names = () + +class Constant(Node): + __slots__ = ('type', 'value', 'coord', '__weakref__') + def __init__(self, type, value, coord=None): + self.type = type + self.value = value + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ('type', 'value', ) + +class Continue(Node): + __slots__ = ('coord', '__weakref__') + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + +class Decl(Node): + __slots__ = ('name', 'quals', 'storage', 'funcspec', 'type', 'init', 'bitsize', 'coord', '__weakref__') + def __init__(self, name, quals, storage, funcspec, type, init, bitsize, coord=None): + self.name = name + self.quals = quals + self.storage = storage + self.funcspec = funcspec + self.type = type + self.init = init + self.bitsize = bitsize + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + if self.init is not None: nodelist.append(("init", self.init)) + if self.bitsize is not None: nodelist.append(("bitsize", self.bitsize)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + if self.init is not None: + yield self.init + if self.bitsize is not None: + yield self.bitsize + + attr_names = ('name', 'quals', 'storage', 'funcspec', ) + +class DeclList(Node): + __slots__ = ('decls', 'coord', '__weakref__') + def __init__(self, decls, coord=None): + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append(("decls[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.decls or []): + yield child + + attr_names = () + +class Default(Node): + __slots__ = ('stmts', 'coord', '__weakref__') + def __init__(self, stmts, coord=None): + self.stmts = stmts + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.stmts or []): + nodelist.append(("stmts[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.stmts or []): + yield child + + attr_names = () + +class DoWhile(Node): + __slots__ = ('cond', 'stmt', 'coord', '__weakref__') + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.stmt is not None: nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + +class EllipsisParam(Node): + __slots__ = ('coord', '__weakref__') + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + +class EmptyStatement(Node): + __slots__ = ('coord', '__weakref__') + def __init__(self, coord=None): + self.coord = coord + + def children(self): + return () + + def __iter__(self): + return + yield + + attr_names = () + +class Enum(Node): + __slots__ = ('name', 'values', 'coord', '__weakref__') + def __init__(self, name, values, coord=None): + self.name = name + self.values = values + self.coord = coord + + def children(self): + nodelist = [] + if self.values is not None: nodelist.append(("values", self.values)) + return tuple(nodelist) + + def __iter__(self): + if self.values is not None: + yield self.values + + attr_names = ('name', ) + +class Enumerator(Node): + __slots__ = ('name', 'value', 'coord', '__weakref__') + def __init__(self, name, value, coord=None): + self.name = name + self.value = value + self.coord = coord + + def children(self): + nodelist = [] + if self.value is not None: nodelist.append(("value", self.value)) + return tuple(nodelist) + + def __iter__(self): + if self.value is not None: + yield self.value + + attr_names = ('name', ) + +class EnumeratorList(Node): + __slots__ = ('enumerators', 'coord', '__weakref__') + def __init__(self, enumerators, coord=None): + self.enumerators = enumerators + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.enumerators or []): + nodelist.append(("enumerators[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.enumerators or []): + yield child + + attr_names = () + +class ExprList(Node): + __slots__ = ('exprs', 'coord', '__weakref__') + def __init__(self, exprs, coord=None): + self.exprs = exprs + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.exprs or []): + nodelist.append(("exprs[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.exprs or []): + yield child + + attr_names = () + +class FileAST(Node): + __slots__ = ('ext', 'coord', '__weakref__') + def __init__(self, ext, coord=None): + self.ext = ext + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.ext or []): + nodelist.append(("ext[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.ext or []): + yield child + + attr_names = () + +class For(Node): + __slots__ = ('init', 'cond', 'next', 'stmt', 'coord', '__weakref__') + def __init__(self, init, cond, next, stmt, coord=None): + self.init = init + self.cond = cond + self.next = next + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.init is not None: nodelist.append(("init", self.init)) + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.next is not None: nodelist.append(("next", self.next)) + if self.stmt is not None: nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.init is not None: + yield self.init + if self.cond is not None: + yield self.cond + if self.next is not None: + yield self.next + if self.stmt is not None: + yield self.stmt + + attr_names = () + +class FuncCall(Node): + __slots__ = ('name', 'args', 'coord', '__weakref__') + def __init__(self, name, args, coord=None): + self.name = name + self.args = args + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: nodelist.append(("name", self.name)) + if self.args is not None: nodelist.append(("args", self.args)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.args is not None: + yield self.args + + attr_names = () + +class FuncDecl(Node): + __slots__ = ('args', 'type', 'coord', '__weakref__') + def __init__(self, args, type, coord=None): + self.args = args + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.args is not None: nodelist.append(("args", self.args)) + if self.type is not None: nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.args is not None: + yield self.args + if self.type is not None: + yield self.type + + attr_names = () + +class FuncDef(Node): + __slots__ = ('decl', 'param_decls', 'body', 'coord', '__weakref__') + def __init__(self, decl, param_decls, body, coord=None): + self.decl = decl + self.param_decls = param_decls + self.body = body + self.coord = coord + + def children(self): + nodelist = [] + if self.decl is not None: nodelist.append(("decl", self.decl)) + if self.body is not None: nodelist.append(("body", self.body)) + for i, child in enumerate(self.param_decls or []): + nodelist.append(("param_decls[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + if self.decl is not None: + yield self.decl + if self.body is not None: + yield self.body + for child in (self.param_decls or []): + yield child + + attr_names = () + +class Goto(Node): + __slots__ = ('name', 'coord', '__weakref__') + def __init__(self, name, coord=None): + self.name = name + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ('name', ) + +class ID(Node): + __slots__ = ('name', 'coord', '__weakref__') + def __init__(self, name, coord=None): + self.name = name + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ('name', ) + +class IdentifierType(Node): + __slots__ = ('names', 'coord', '__weakref__') + def __init__(self, names, coord=None): + self.names = names + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ('names', ) + +class If(Node): + __slots__ = ('cond', 'iftrue', 'iffalse', 'coord', '__weakref__') + def __init__(self, cond, iftrue, iffalse, coord=None): + self.cond = cond + self.iftrue = iftrue + self.iffalse = iffalse + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.iftrue is not None: nodelist.append(("iftrue", self.iftrue)) + if self.iffalse is not None: nodelist.append(("iffalse", self.iffalse)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.iftrue is not None: + yield self.iftrue + if self.iffalse is not None: + yield self.iffalse + + attr_names = () + +class InitList(Node): + __slots__ = ('exprs', 'coord', '__weakref__') + def __init__(self, exprs, coord=None): + self.exprs = exprs + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.exprs or []): + nodelist.append(("exprs[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.exprs or []): + yield child + + attr_names = () + +class Label(Node): + __slots__ = ('name', 'stmt', 'coord', '__weakref__') + def __init__(self, name, stmt, coord=None): + self.name = name + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.stmt is not None: nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.stmt is not None: + yield self.stmt + + attr_names = ('name', ) + +class NamedInitializer(Node): + __slots__ = ('name', 'expr', 'coord', '__weakref__') + def __init__(self, name, expr, coord=None): + self.name = name + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: nodelist.append(("expr", self.expr)) + for i, child in enumerate(self.name or []): + nodelist.append(("name[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + for child in (self.name or []): + yield child + + attr_names = () + +class ParamList(Node): + __slots__ = ('params', 'coord', '__weakref__') + def __init__(self, params, coord=None): + self.params = params + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.params or []): + nodelist.append(("params[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.params or []): + yield child + + attr_names = () + +class PtrDecl(Node): + __slots__ = ('quals', 'type', 'coord', '__weakref__') + def __init__(self, quals, type, coord=None): + self.quals = quals + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ('quals', ) + +class Return(Node): + __slots__ = ('expr', 'coord', '__weakref__') + def __init__(self, expr, coord=None): + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + + attr_names = () + +class Struct(Node): + __slots__ = ('name', 'decls', 'coord', '__weakref__') + def __init__(self, name, decls, coord=None): + self.name = name + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append(("decls[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.decls or []): + yield child + + attr_names = ('name', ) + +class StructRef(Node): + __slots__ = ('name', 'type', 'field', 'coord', '__weakref__') + def __init__(self, name, type, field, coord=None): + self.name = name + self.type = type + self.field = field + self.coord = coord + + def children(self): + nodelist = [] + if self.name is not None: nodelist.append(("name", self.name)) + if self.field is not None: nodelist.append(("field", self.field)) + return tuple(nodelist) + + def __iter__(self): + if self.name is not None: + yield self.name + if self.field is not None: + yield self.field + + attr_names = ('type', ) + +class Switch(Node): + __slots__ = ('cond', 'stmt', 'coord', '__weakref__') + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.stmt is not None: nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + +class TernaryOp(Node): + __slots__ = ('cond', 'iftrue', 'iffalse', 'coord', '__weakref__') + def __init__(self, cond, iftrue, iffalse, coord=None): + self.cond = cond + self.iftrue = iftrue + self.iffalse = iffalse + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.iftrue is not None: nodelist.append(("iftrue", self.iftrue)) + if self.iffalse is not None: nodelist.append(("iffalse", self.iffalse)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.iftrue is not None: + yield self.iftrue + if self.iffalse is not None: + yield self.iffalse + + attr_names = () + +class TypeDecl(Node): + __slots__ = ('declname', 'quals', 'type', 'coord', '__weakref__') + def __init__(self, declname, quals, type, coord=None): + self.declname = declname + self.quals = quals + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ('declname', 'quals', ) + +class Typedef(Node): + __slots__ = ('name', 'quals', 'storage', 'type', 'coord', '__weakref__') + def __init__(self, name, quals, storage, type, coord=None): + self.name = name + self.quals = quals + self.storage = storage + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ('name', 'quals', 'storage', ) + +class Typename(Node): + __slots__ = ('name', 'quals', 'type', 'coord', '__weakref__') + def __init__(self, name, quals, type, coord=None): + self.name = name + self.quals = quals + self.type = type + self.coord = coord + + def children(self): + nodelist = [] + if self.type is not None: nodelist.append(("type", self.type)) + return tuple(nodelist) + + def __iter__(self): + if self.type is not None: + yield self.type + + attr_names = ('name', 'quals', ) + +class UnaryOp(Node): + __slots__ = ('op', 'expr', 'coord', '__weakref__') + def __init__(self, op, expr, coord=None): + self.op = op + self.expr = expr + self.coord = coord + + def children(self): + nodelist = [] + if self.expr is not None: nodelist.append(("expr", self.expr)) + return tuple(nodelist) + + def __iter__(self): + if self.expr is not None: + yield self.expr + + attr_names = ('op', ) + +class Union(Node): + __slots__ = ('name', 'decls', 'coord', '__weakref__') + def __init__(self, name, decls, coord=None): + self.name = name + self.decls = decls + self.coord = coord + + def children(self): + nodelist = [] + for i, child in enumerate(self.decls or []): + nodelist.append(("decls[%d]" % i, child)) + return tuple(nodelist) + + def __iter__(self): + for child in (self.decls or []): + yield child + + attr_names = ('name', ) + +class While(Node): + __slots__ = ('cond', 'stmt', 'coord', '__weakref__') + def __init__(self, cond, stmt, coord=None): + self.cond = cond + self.stmt = stmt + self.coord = coord + + def children(self): + nodelist = [] + if self.cond is not None: nodelist.append(("cond", self.cond)) + if self.stmt is not None: nodelist.append(("stmt", self.stmt)) + return tuple(nodelist) + + def __iter__(self): + if self.cond is not None: + yield self.cond + if self.stmt is not None: + yield self.stmt + + attr_names = () + +class Pragma(Node): + __slots__ = ('string', 'coord', '__weakref__') + def __init__(self, string, coord=None): + self.string = string + self.coord = coord + + def children(self): + nodelist = [] + return tuple(nodelist) + + def __iter__(self): + return + yield + + attr_names = ('string', ) + diff --git a/pycparser/c_generator.py b/pycparser/c_generator.py new file mode 100644 index 0000000..f789742 --- /dev/null +++ b/pycparser/c_generator.py @@ -0,0 +1,428 @@ +#------------------------------------------------------------------------------ +# pycparser: c_generator.py +# +# C code generator from pycparser AST nodes. +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------ +from . import c_ast + + +class CGenerator(object): + """ Uses the same visitor pattern as c_ast.NodeVisitor, but modified to + return a value from each visit method, using string accumulation in + generic_visit. + """ + def __init__(self): + # Statements start with indentation of self.indent_level spaces, using + # the _make_indent method + # + self.indent_level = 0 + + def _make_indent(self): + return ' ' * self.indent_level + + def visit(self, node): + method = 'visit_' + node.__class__.__name__ + return getattr(self, method, self.generic_visit)(node) + + def generic_visit(self, node): + #~ print('generic:', type(node)) + if node is None: + return '' + else: + return ''.join(self.visit(c) for c_name, c in node.children()) + + def visit_Constant(self, n): + return n.value + + def visit_ID(self, n): + return n.name + + def visit_Pragma(self, n): + ret = '#pragma' + if n.string: + ret += ' ' + n.string + return ret + + def visit_ArrayRef(self, n): + arrref = self._parenthesize_unless_simple(n.name) + return arrref + '[' + self.visit(n.subscript) + ']' + + def visit_StructRef(self, n): + sref = self._parenthesize_unless_simple(n.name) + return sref + n.type + self.visit(n.field) + + def visit_FuncCall(self, n): + fref = self._parenthesize_unless_simple(n.name) + return fref + '(' + self.visit(n.args) + ')' + + def visit_UnaryOp(self, n): + operand = self._parenthesize_unless_simple(n.expr) + if n.op == 'p++': + return '%s++' % operand + elif n.op == 'p--': + return '%s--' % operand + elif n.op == 'sizeof': + # Always parenthesize the argument of sizeof since it can be + # a name. + return 'sizeof(%s)' % self.visit(n.expr) + else: + return '%s%s' % (n.op, operand) + + def visit_BinaryOp(self, n): + lval_str = self._parenthesize_if(n.left, + lambda d: not self._is_simple_node(d)) + rval_str = self._parenthesize_if(n.right, + lambda d: not self._is_simple_node(d)) + return '%s %s %s' % (lval_str, n.op, rval_str) + + def visit_Assignment(self, n): + rval_str = self._parenthesize_if( + n.rvalue, + lambda n: isinstance(n, c_ast.Assignment)) + return '%s %s %s' % (self.visit(n.lvalue), n.op, rval_str) + + def visit_IdentifierType(self, n): + return ' '.join(n.names) + + def _visit_expr(self, n): + if isinstance(n, c_ast.InitList): + return '{' + self.visit(n) + '}' + elif isinstance(n, c_ast.ExprList): + return '(' + self.visit(n) + ')' + else: + return self.visit(n) + + def visit_Decl(self, n, no_type=False): + # no_type is used when a Decl is part of a DeclList, where the type is + # explicitly only for the first declaration in a list. + # + s = n.name if no_type else self._generate_decl(n) + if n.bitsize: s += ' : ' + self.visit(n.bitsize) + if n.init: + s += ' = ' + self._visit_expr(n.init) + return s + + def visit_DeclList(self, n): + s = self.visit(n.decls[0]) + if len(n.decls) > 1: + s += ', ' + ', '.join(self.visit_Decl(decl, no_type=True) + for decl in n.decls[1:]) + return s + + def visit_Typedef(self, n): + s = '' + if n.storage: s += ' '.join(n.storage) + ' ' + s += self._generate_type(n.type) + return s + + def visit_Cast(self, n): + s = '(' + self._generate_type(n.to_type) + ')' + return s + ' ' + self._parenthesize_unless_simple(n.expr) + + def visit_ExprList(self, n): + visited_subexprs = [] + for expr in n.exprs: + visited_subexprs.append(self._visit_expr(expr)) + return ', '.join(visited_subexprs) + + def visit_InitList(self, n): + visited_subexprs = [] + for expr in n.exprs: + visited_subexprs.append(self._visit_expr(expr)) + return ', '.join(visited_subexprs) + + def visit_Enum(self, n): + return self._generate_struct_union_enum(n, name='enum') + + def visit_Enumerator(self, n): + if not n.value: + return '{indent}{name},\n'.format( + indent=self._make_indent(), + name=n.name, + ) + else: + return '{indent}{name} = {value},\n'.format( + indent=self._make_indent(), + name=n.name, + value=self.visit(n.value), + ) + + def visit_FuncDef(self, n): + decl = self.visit(n.decl) + self.indent_level = 0 + body = self.visit(n.body) + if n.param_decls: + knrdecls = ';\n'.join(self.visit(p) for p in n.param_decls) + return decl + '\n' + knrdecls + ';\n' + body + '\n' + else: + return decl + '\n' + body + '\n' + + def visit_FileAST(self, n): + s = '' + for ext in n.ext: + if isinstance(ext, c_ast.FuncDef): + s += self.visit(ext) + elif isinstance(ext, c_ast.Pragma): + s += self.visit(ext) + '\n' + else: + s += self.visit(ext) + ';\n' + return s + + def visit_Compound(self, n): + s = self._make_indent() + '{\n' + self.indent_level += 2 + if n.block_items: + s += ''.join(self._generate_stmt(stmt) for stmt in n.block_items) + self.indent_level -= 2 + s += self._make_indent() + '}\n' + return s + + def visit_CompoundLiteral(self, n): + return '(' + self.visit(n.type) + '){' + self.visit(n.init) + '}' + + + def visit_EmptyStatement(self, n): + return ';' + + def visit_ParamList(self, n): + return ', '.join(self.visit(param) for param in n.params) + + def visit_Return(self, n): + s = 'return' + if n.expr: s += ' ' + self.visit(n.expr) + return s + ';' + + def visit_Break(self, n): + return 'break;' + + def visit_Continue(self, n): + return 'continue;' + + def visit_TernaryOp(self, n): + s = '(' + self._visit_expr(n.cond) + ') ? ' + s += '(' + self._visit_expr(n.iftrue) + ') : ' + s += '(' + self._visit_expr(n.iffalse) + ')' + return s + + def visit_If(self, n): + s = 'if (' + if n.cond: s += self.visit(n.cond) + s += ')\n' + s += self._generate_stmt(n.iftrue, add_indent=True) + if n.iffalse: + s += self._make_indent() + 'else\n' + s += self._generate_stmt(n.iffalse, add_indent=True) + return s + + def visit_For(self, n): + s = 'for (' + if n.init: s += self.visit(n.init) + s += ';' + if n.cond: s += ' ' + self.visit(n.cond) + s += ';' + if n.next: s += ' ' + self.visit(n.next) + s += ')\n' + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_While(self, n): + s = 'while (' + if n.cond: s += self.visit(n.cond) + s += ')\n' + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_DoWhile(self, n): + s = 'do\n' + s += self._generate_stmt(n.stmt, add_indent=True) + s += self._make_indent() + 'while (' + if n.cond: s += self.visit(n.cond) + s += ');' + return s + + def visit_Switch(self, n): + s = 'switch (' + self.visit(n.cond) + ')\n' + s += self._generate_stmt(n.stmt, add_indent=True) + return s + + def visit_Case(self, n): + s = 'case ' + self.visit(n.expr) + ':\n' + for stmt in n.stmts: + s += self._generate_stmt(stmt, add_indent=True) + return s + + def visit_Default(self, n): + s = 'default:\n' + for stmt in n.stmts: + s += self._generate_stmt(stmt, add_indent=True) + return s + + def visit_Label(self, n): + return n.name + ':\n' + self._generate_stmt(n.stmt) + + def visit_Goto(self, n): + return 'goto ' + n.name + ';' + + def visit_EllipsisParam(self, n): + return '...' + + def visit_Struct(self, n): + return self._generate_struct_union_enum(n, 'struct') + + def visit_Typename(self, n): + return self._generate_type(n.type) + + def visit_Union(self, n): + return self._generate_struct_union_enum(n, 'union') + + def visit_NamedInitializer(self, n): + s = '' + for name in n.name: + if isinstance(name, c_ast.ID): + s += '.' + name.name + else: + s += '[' + self.visit(name) + ']' + s += ' = ' + self._visit_expr(n.expr) + return s + + def visit_FuncDecl(self, n): + return self._generate_type(n) + + def _generate_struct_union_enum(self, n, name): + """ Generates code for structs, unions, and enums. name should be + 'struct', 'union', or 'enum'. + """ + if name in ('struct', 'union'): + members = n.decls + body_function = self._generate_struct_union_body + else: + assert name == 'enum' + members = None if n.values is None else n.values.enumerators + body_function = self._generate_enum_body + s = name + ' ' + (n.name or '') + if members is not None: + # None means no members + # Empty sequence means an empty list of members + s += '\n' + s += self._make_indent() + self.indent_level += 2 + s += '{\n' + s += body_function(members) + self.indent_level -= 2 + s += self._make_indent() + '}' + return s + + def _generate_struct_union_body(self, members): + return ''.join(self._generate_stmt(decl) for decl in members) + + def _generate_enum_body(self, members): + # `[:-2] + '\n'` removes the final `,` from the enumerator list + return ''.join(self.visit(value) for value in members)[:-2] + '\n' + + def _generate_stmt(self, n, add_indent=False): + """ Generation from a statement node. This method exists as a wrapper + for individual visit_* methods to handle different treatment of + some statements in this context. + """ + typ = type(n) + if add_indent: self.indent_level += 2 + indent = self._make_indent() + if add_indent: self.indent_level -= 2 + + if typ in ( + c_ast.Decl, c_ast.Assignment, c_ast.Cast, c_ast.UnaryOp, + c_ast.BinaryOp, c_ast.TernaryOp, c_ast.FuncCall, c_ast.ArrayRef, + c_ast.StructRef, c_ast.Constant, c_ast.ID, c_ast.Typedef, + c_ast.ExprList): + # These can also appear in an expression context so no semicolon + # is added to them automatically + # + return indent + self.visit(n) + ';\n' + elif typ in (c_ast.Compound,): + # No extra indentation required before the opening brace of a + # compound - because it consists of multiple lines it has to + # compute its own indentation. + # + return self.visit(n) + else: + return indent + self.visit(n) + '\n' + + def _generate_decl(self, n): + """ Generation from a Decl node. + """ + s = '' + if n.funcspec: s = ' '.join(n.funcspec) + ' ' + if n.storage: s += ' '.join(n.storage) + ' ' + s += self._generate_type(n.type) + return s + + def _generate_type(self, n, modifiers=[]): + """ Recursive generation from a type node. n is the type node. + modifiers collects the PtrDecl, ArrayDecl and FuncDecl modifiers + encountered on the way down to a TypeDecl, to allow proper + generation from it. + """ + typ = type(n) + #~ print(n, modifiers) + + if typ == c_ast.TypeDecl: + s = '' + if n.quals: s += ' '.join(n.quals) + ' ' + s += self.visit(n.type) + + nstr = n.declname if n.declname else '' + # Resolve modifiers. + # Wrap in parens to distinguish pointer to array and pointer to + # function syntax. + # + for i, modifier in enumerate(modifiers): + if isinstance(modifier, c_ast.ArrayDecl): + if (i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl)): + nstr = '(' + nstr + ')' + nstr += '[' + self.visit(modifier.dim) + ']' + elif isinstance(modifier, c_ast.FuncDecl): + if (i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl)): + nstr = '(' + nstr + ')' + nstr += '(' + self.visit(modifier.args) + ')' + elif isinstance(modifier, c_ast.PtrDecl): + if modifier.quals: + nstr = '* %s %s' % (' '.join(modifier.quals), nstr) + else: + nstr = '*' + nstr + if nstr: s += ' ' + nstr + return s + elif typ == c_ast.Decl: + return self._generate_decl(n.type) + elif typ == c_ast.Typename: + return self._generate_type(n.type) + elif typ == c_ast.IdentifierType: + return ' '.join(n.names) + ' ' + elif typ in (c_ast.ArrayDecl, c_ast.PtrDecl, c_ast.FuncDecl): + return self._generate_type(n.type, modifiers + [n]) + else: + return self.visit(n) + + def _parenthesize_if(self, n, condition): + """ Visits 'n' and returns its string representation, parenthesized + if the condition function applied to the node returns True. + """ + s = self._visit_expr(n) + if condition(n): + return '(' + s + ')' + else: + return s + + def _parenthesize_unless_simple(self, n): + """ Common use case for _parenthesize_if + """ + return self._parenthesize_if(n, lambda d: not self._is_simple_node(d)) + + def _is_simple_node(self, n): + """ Returns True for nodes that are "simple" - i.e. nodes that always + have higher precedence than operators. + """ + return isinstance(n, (c_ast.Constant, c_ast.ID, c_ast.ArrayRef, + c_ast.StructRef, c_ast.FuncCall)) diff --git a/pycparser/c_lexer.py b/pycparser/c_lexer.py new file mode 100644 index 0000000..de8445e --- /dev/null +++ b/pycparser/c_lexer.py @@ -0,0 +1,484 @@ +#------------------------------------------------------------------------------ +# pycparser: c_lexer.py +# +# CLexer class: lexer for the C language +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------ +import re +import sys + +from .ply import lex +from .ply.lex import TOKEN + + +class CLexer(object): + """ A lexer for the C language. After building it, set the + input text with input(), and call token() to get new + tokens. + + The public attribute filename can be set to an initial + filaneme, but the lexer will update it upon #line + directives. + """ + def __init__(self, error_func, on_lbrace_func, on_rbrace_func, + type_lookup_func): + """ Create a new Lexer. + + error_func: + An error function. Will be called with an error + message, line and column as arguments, in case of + an error during lexing. + + on_lbrace_func, on_rbrace_func: + Called when an LBRACE or RBRACE is encountered + (likely to push/pop type_lookup_func's scope) + + type_lookup_func: + A type lookup function. Given a string, it must + return True IFF this string is a name of a type + that was defined with a typedef earlier. + """ + self.error_func = error_func + self.on_lbrace_func = on_lbrace_func + self.on_rbrace_func = on_rbrace_func + self.type_lookup_func = type_lookup_func + self.filename = '' + + # Keeps track of the last token returned from self.token() + self.last_token = None + + # Allow either "# line" or "# <num>" to support GCC's + # cpp output + # + self.line_pattern = re.compile(r'([ \t]*line\W)|([ \t]*\d+)') + self.pragma_pattern = re.compile(r'[ \t]*pragma\W') + + def build(self, **kwargs): + """ Builds the lexer from the specification. Must be + called after the lexer object is created. + + This method exists separately, because the PLY + manual warns against calling lex.lex inside + __init__ + """ + self.lexer = lex.lex(object=self, **kwargs) + + def reset_lineno(self): + """ Resets the internal line number counter of the lexer. + """ + self.lexer.lineno = 1 + + def input(self, text): + self.lexer.input(text) + + def token(self): + self.last_token = self.lexer.token() + return self.last_token + + def find_tok_column(self, token): + """ Find the column of the token in its line. + """ + last_cr = self.lexer.lexdata.rfind('\n', 0, token.lexpos) + return token.lexpos - last_cr + + ######################-- PRIVATE --###################### + + ## + ## Internal auxiliary methods + ## + def _error(self, msg, token): + location = self._make_tok_location(token) + self.error_func(msg, location[0], location[1]) + self.lexer.skip(1) + + def _make_tok_location(self, token): + return (token.lineno, self.find_tok_column(token)) + + ## + ## Reserved keywords + ## + keywords = ( + '_BOOL', '_COMPLEX', 'AUTO', 'BREAK', 'CASE', 'CHAR', 'CONST', + 'CONTINUE', 'DEFAULT', 'DO', 'DOUBLE', 'ELSE', 'ENUM', 'EXTERN', + 'FLOAT', 'FOR', 'GOTO', 'IF', 'INLINE', 'INT', 'LONG', + 'REGISTER', 'OFFSETOF', + 'RESTRICT', 'RETURN', 'SHORT', 'SIGNED', 'SIZEOF', 'STATIC', 'STRUCT', + 'SWITCH', 'TYPEDEF', 'UNION', 'UNSIGNED', 'VOID', + 'VOLATILE', 'WHILE', '__INT128', + ) + + keyword_map = {} + for keyword in keywords: + if keyword == '_BOOL': + keyword_map['_Bool'] = keyword + elif keyword == '_COMPLEX': + keyword_map['_Complex'] = keyword + else: + keyword_map[keyword.lower()] = keyword + + ## + ## All the tokens recognized by the lexer + ## + tokens = keywords + ( + # Identifiers + 'ID', + + # Type identifiers (identifiers previously defined as + # types with typedef) + 'TYPEID', + + # constants + 'INT_CONST_DEC', 'INT_CONST_OCT', 'INT_CONST_HEX', 'INT_CONST_BIN', + 'FLOAT_CONST', 'HEX_FLOAT_CONST', + 'CHAR_CONST', + 'WCHAR_CONST', + + # String literals + 'STRING_LITERAL', + 'WSTRING_LITERAL', + + # Operators + 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MOD', + 'OR', 'AND', 'NOT', 'XOR', 'LSHIFT', 'RSHIFT', + 'LOR', 'LAND', 'LNOT', + 'LT', 'LE', 'GT', 'GE', 'EQ', 'NE', + + # Assignment + 'EQUALS', 'TIMESEQUAL', 'DIVEQUAL', 'MODEQUAL', + 'PLUSEQUAL', 'MINUSEQUAL', + 'LSHIFTEQUAL','RSHIFTEQUAL', 'ANDEQUAL', 'XOREQUAL', + 'OREQUAL', + + # Increment/decrement + 'PLUSPLUS', 'MINUSMINUS', + + # Structure dereference (->) + 'ARROW', + + # Conditional operator (?) + 'CONDOP', + + # Delimeters + 'LPAREN', 'RPAREN', # ( ) + 'LBRACKET', 'RBRACKET', # [ ] + 'LBRACE', 'RBRACE', # { } + 'COMMA', 'PERIOD', # . , + 'SEMI', 'COLON', # ; : + + # Ellipsis (...) + 'ELLIPSIS', + + # pre-processor + 'PPHASH', # '#' + 'PPPRAGMA', # 'pragma' + 'PPPRAGMASTR', + ) + + ## + ## Regexes for use in tokens + ## + ## + + # valid C identifiers (K&R2: A.2.3), plus '$' (supported by some compilers) + identifier = r'[a-zA-Z_$][0-9a-zA-Z_$]*' + + hex_prefix = '0[xX]' + hex_digits = '[0-9a-fA-F]+' + bin_prefix = '0[bB]' + bin_digits = '[01]+' + + # integer constants (K&R2: A.2.5.1) + integer_suffix_opt = r'(([uU]ll)|([uU]LL)|(ll[uU]?)|(LL[uU]?)|([uU][lL])|([lL][uU]?)|[uU])?' + decimal_constant = '(0'+integer_suffix_opt+')|([1-9][0-9]*'+integer_suffix_opt+')' + octal_constant = '0[0-7]*'+integer_suffix_opt + hex_constant = hex_prefix+hex_digits+integer_suffix_opt + bin_constant = bin_prefix+bin_digits+integer_suffix_opt + + bad_octal_constant = '0[0-7]*[89]' + + # character constants (K&R2: A.2.5.2) + # Note: a-zA-Z and '.-~^_!=&;,' are allowed as escape chars to support #line + # directives with Windows paths as filenames (..\..\dir\file) + # For the same reason, decimal_escape allows all digit sequences. We want to + # parse all correct code, even if it means to sometimes parse incorrect + # code. + # + simple_escape = r"""([a-zA-Z._~!=&\^\-\\?'"])""" + decimal_escape = r"""(\d+)""" + hex_escape = r"""(x[0-9a-fA-F]+)""" + bad_escape = r"""([\\][^a-zA-Z._~^!=&\^\-\\?'"x0-7])""" + + escape_sequence = r"""(\\("""+simple_escape+'|'+decimal_escape+'|'+hex_escape+'))' + cconst_char = r"""([^'\\\n]|"""+escape_sequence+')' + char_const = "'"+cconst_char+"'" + wchar_const = 'L'+char_const + unmatched_quote = "('"+cconst_char+"*\\n)|('"+cconst_char+"*$)" + bad_char_const = r"""('"""+cconst_char+"""[^'\n]+')|('')|('"""+bad_escape+r"""[^'\n]*')""" + + # string literals (K&R2: A.2.6) + string_char = r"""([^"\\\n]|"""+escape_sequence+')' + string_literal = '"'+string_char+'*"' + wstring_literal = 'L'+string_literal + bad_string_literal = '"'+string_char+'*?'+bad_escape+string_char+'*"' + + # floating constants (K&R2: A.2.5.3) + exponent_part = r"""([eE][-+]?[0-9]+)""" + fractional_constant = r"""([0-9]*\.[0-9]+)|([0-9]+\.)""" + floating_constant = '(((('+fractional_constant+')'+exponent_part+'?)|([0-9]+'+exponent_part+'))[FfLl]?)' + binary_exponent_part = r'''([pP][+-]?[0-9]+)''' + hex_fractional_constant = '((('+hex_digits+r""")?\."""+hex_digits+')|('+hex_digits+r"""\.))""" + hex_floating_constant = '('+hex_prefix+'('+hex_digits+'|'+hex_fractional_constant+')'+binary_exponent_part+'[FfLl]?)' + + ## + ## Lexer states: used for preprocessor \n-terminated directives + ## + states = ( + # ppline: preprocessor line directives + # + ('ppline', 'exclusive'), + + # pppragma: pragma + # + ('pppragma', 'exclusive'), + ) + + def t_PPHASH(self, t): + r'[ \t]*\#' + if self.line_pattern.match(t.lexer.lexdata, pos=t.lexer.lexpos): + t.lexer.begin('ppline') + self.pp_line = self.pp_filename = None + elif self.pragma_pattern.match(t.lexer.lexdata, pos=t.lexer.lexpos): + t.lexer.begin('pppragma') + else: + t.type = 'PPHASH' + return t + + ## + ## Rules for the ppline state + ## + @TOKEN(string_literal) + def t_ppline_FILENAME(self, t): + if self.pp_line is None: + self._error('filename before line number in #line', t) + else: + self.pp_filename = t.value.lstrip('"').rstrip('"') + + @TOKEN(decimal_constant) + def t_ppline_LINE_NUMBER(self, t): + if self.pp_line is None: + self.pp_line = t.value + else: + # Ignore: GCC's cpp sometimes inserts a numeric flag + # after the file name + pass + + def t_ppline_NEWLINE(self, t): + r'\n' + if self.pp_line is None: + self._error('line number missing in #line', t) + else: + self.lexer.lineno = int(self.pp_line) + + if self.pp_filename is not None: + self.filename = self.pp_filename + + t.lexer.begin('INITIAL') + + def t_ppline_PPLINE(self, t): + r'line' + pass + + t_ppline_ignore = ' \t' + + def t_ppline_error(self, t): + self._error('invalid #line directive', t) + + ## + ## Rules for the pppragma state + ## + def t_pppragma_NEWLINE(self, t): + r'\n' + t.lexer.lineno += 1 + t.lexer.begin('INITIAL') + + def t_pppragma_PPPRAGMA(self, t): + r'pragma' + return t + + t_pppragma_ignore = ' \t' + + def t_pppragma_STR(self, t): + '.+' + t.type = 'PPPRAGMASTR' + return t + + def t_pppragma_error(self, t): + self._error('invalid #pragma directive', t) + + ## + ## Rules for the normal state + ## + t_ignore = ' \t' + + # Newlines + def t_NEWLINE(self, t): + r'\n+' + t.lexer.lineno += t.value.count("\n") + + # Operators + t_PLUS = r'\+' + t_MINUS = r'-' + t_TIMES = r'\*' + t_DIVIDE = r'/' + t_MOD = r'%' + t_OR = r'\|' + t_AND = r'&' + t_NOT = r'~' + t_XOR = r'\^' + t_LSHIFT = r'<<' + t_RSHIFT = r'>>' + t_LOR = r'\|\|' + t_LAND = r'&&' + t_LNOT = r'!' + t_LT = r'<' + t_GT = r'>' + t_LE = r'<=' + t_GE = r'>=' + t_EQ = r'==' + t_NE = r'!=' + + # Assignment operators + t_EQUALS = r'=' + t_TIMESEQUAL = r'\*=' + t_DIVEQUAL = r'/=' + t_MODEQUAL = r'%=' + t_PLUSEQUAL = r'\+=' + t_MINUSEQUAL = r'-=' + t_LSHIFTEQUAL = r'<<=' + t_RSHIFTEQUAL = r'>>=' + t_ANDEQUAL = r'&=' + t_OREQUAL = r'\|=' + t_XOREQUAL = r'\^=' + + # Increment/decrement + t_PLUSPLUS = r'\+\+' + t_MINUSMINUS = r'--' + + # -> + t_ARROW = r'->' + + # ? + t_CONDOP = r'\?' + + # Delimeters + t_LPAREN = r'\(' + t_RPAREN = r'\)' + t_LBRACKET = r'\[' + t_RBRACKET = r'\]' + t_COMMA = r',' + t_PERIOD = r'\.' + t_SEMI = r';' + t_COLON = r':' + t_ELLIPSIS = r'\.\.\.' + + # Scope delimiters + # To see why on_lbrace_func is needed, consider: + # typedef char TT; + # void foo(int TT) { TT = 10; } + # TT x = 5; + # Outside the function, TT is a typedef, but inside (starting and ending + # with the braces) it's a parameter. The trouble begins with yacc's + # lookahead token. If we open a new scope in brace_open, then TT has + # already been read and incorrectly interpreted as TYPEID. So, we need + # to open and close scopes from within the lexer. + # Similar for the TT immediately outside the end of the function. + # + @TOKEN(r'\{') + def t_LBRACE(self, t): + self.on_lbrace_func() + return t + @TOKEN(r'\}') + def t_RBRACE(self, t): + self.on_rbrace_func() + return t + + t_STRING_LITERAL = string_literal + + # The following floating and integer constants are defined as + # functions to impose a strict order (otherwise, decimal + # is placed before the others because its regex is longer, + # and this is bad) + # + @TOKEN(floating_constant) + def t_FLOAT_CONST(self, t): + return t + + @TOKEN(hex_floating_constant) + def t_HEX_FLOAT_CONST(self, t): + return t + + @TOKEN(hex_constant) + def t_INT_CONST_HEX(self, t): + return t + + @TOKEN(bin_constant) + def t_INT_CONST_BIN(self, t): + return t + + @TOKEN(bad_octal_constant) + def t_BAD_CONST_OCT(self, t): + msg = "Invalid octal constant" + self._error(msg, t) + + @TOKEN(octal_constant) + def t_INT_CONST_OCT(self, t): + return t + + @TOKEN(decimal_constant) + def t_INT_CONST_DEC(self, t): + return t + + # Must come before bad_char_const, to prevent it from + # catching valid char constants as invalid + # + @TOKEN(char_const) + def t_CHAR_CONST(self, t): + return t + + @TOKEN(wchar_const) + def t_WCHAR_CONST(self, t): + return t + + @TOKEN(unmatched_quote) + def t_UNMATCHED_QUOTE(self, t): + msg = "Unmatched '" + self._error(msg, t) + + @TOKEN(bad_char_const) + def t_BAD_CHAR_CONST(self, t): + msg = "Invalid char constant %s" % t.value + self._error(msg, t) + + @TOKEN(wstring_literal) + def t_WSTRING_LITERAL(self, t): + return t + + # unmatched string literals are caught by the preprocessor + + @TOKEN(bad_string_literal) + def t_BAD_STRING_LITERAL(self, t): + msg = "String contains invalid escape code" + self._error(msg, t) + + @TOKEN(identifier) + def t_ID(self, t): + t.type = self.keyword_map.get(t.value, "ID") + if t.type == 'ID' and self.type_lookup_func(t.value): + t.type = "TYPEID" + return t + + def t_error(self, t): + msg = 'Illegal character %s' % repr(t.value[0]) + self._error(msg, t) diff --git a/pycparser/c_parser.py b/pycparser/c_parser.py new file mode 100644 index 0000000..0e6e755 --- /dev/null +++ b/pycparser/c_parser.py @@ -0,0 +1,1850 @@ +#------------------------------------------------------------------------------ +# pycparser: c_parser.py +# +# CParser class: Parser and AST builder for the C language +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#------------------------------------------------------------------------------ +import re + +from .ply import yacc + +from . import c_ast +from .c_lexer import CLexer +from .plyparser import PLYParser, Coord, ParseError, parameterized, template +from .ast_transforms import fix_switch_cases + + +@template +class CParser(PLYParser): + def __init__( + self, + lex_optimize=True, + lexer=CLexer, + lextab='pycparser.lextab', + yacc_optimize=True, + yacctab='pycparser.yacctab', + yacc_debug=False, + taboutputdir=''): + """ Create a new CParser. + + Some arguments for controlling the debug/optimization + level of the parser are provided. The defaults are + tuned for release/performance mode. + The simple rules for using them are: + *) When tweaking CParser/CLexer, set these to False + *) When releasing a stable parser, set to True + + lex_optimize: + Set to False when you're modifying the lexer. + Otherwise, changes in the lexer won't be used, if + some lextab.py file exists. + When releasing with a stable lexer, set to True + to save the re-generation of the lexer table on + each run. + + lexer: + Set this parameter to define the lexer to use if + you're not using the default CLexer. + + lextab: + Points to the lex table that's used for optimized + mode. Only if you're modifying the lexer and want + some tests to avoid re-generating the table, make + this point to a local lex table file (that's been + earlier generated with lex_optimize=True) + + yacc_optimize: + Set to False when you're modifying the parser. + Otherwise, changes in the parser won't be used, if + some parsetab.py file exists. + When releasing with a stable parser, set to True + to save the re-generation of the parser table on + each run. + + yacctab: + Points to the yacc table that's used for optimized + mode. Only if you're modifying the parser, make + this point to a local yacc table file + + yacc_debug: + Generate a parser.out file that explains how yacc + built the parsing table from the grammar. + + taboutputdir: + Set this parameter to control the location of generated + lextab and yacctab files. + """ + self.clex = lexer( + error_func=self._lex_error_func, + on_lbrace_func=self._lex_on_lbrace_func, + on_rbrace_func=self._lex_on_rbrace_func, + type_lookup_func=self._lex_type_lookup_func) + + self.clex.build( + optimize=lex_optimize, + lextab=lextab, + outputdir=taboutputdir) + self.tokens = self.clex.tokens + + rules_with_opt = [ + 'abstract_declarator', + 'assignment_expression', + 'declaration_list', + 'declaration_specifiers_no_type', + 'designation', + 'expression', + 'identifier_list', + 'init_declarator_list', + 'id_init_declarator_list', + 'initializer_list', + 'parameter_type_list', + 'block_item_list', + 'type_qualifier_list', + 'struct_declarator_list' + ] + + for rule in rules_with_opt: + self._create_opt_rule(rule) + + self.cparser = yacc.yacc( + module=self, + start='translation_unit_or_empty', + debug=yacc_debug, + optimize=yacc_optimize, + tabmodule=yacctab, + outputdir=taboutputdir) + + # Stack of scopes for keeping track of symbols. _scope_stack[-1] is + # the current (topmost) scope. Each scope is a dictionary that + # specifies whether a name is a type. If _scope_stack[n][name] is + # True, 'name' is currently a type in the scope. If it's False, + # 'name' is used in the scope but not as a type (for instance, if we + # saw: int name; + # If 'name' is not a key in _scope_stack[n] then 'name' was not defined + # in this scope at all. + self._scope_stack = [dict()] + + # Keeps track of the last token given to yacc (the lookahead token) + self._last_yielded_token = None + + def parse(self, text, filename='', debuglevel=0): + """ Parses C code and returns an AST. + + text: + A string containing the C source code + + filename: + Name of the file being parsed (for meaningful + error messages) + + debuglevel: + Debug level to yacc + """ + self.clex.filename = filename + self.clex.reset_lineno() + self._scope_stack = [dict()] + self._last_yielded_token = None + return self.cparser.parse( + input=text, + lexer=self.clex, + debug=debuglevel) + + ######################-- PRIVATE --###################### + + def _push_scope(self): + self._scope_stack.append(dict()) + + def _pop_scope(self): + assert len(self._scope_stack) > 1 + self._scope_stack.pop() + + def _add_typedef_name(self, name, coord): + """ Add a new typedef name (ie a TYPEID) to the current scope + """ + if not self._scope_stack[-1].get(name, True): + self._parse_error( + "Typedef %r previously declared as non-typedef " + "in this scope" % name, coord) + self._scope_stack[-1][name] = True + + def _add_identifier(self, name, coord): + """ Add a new object, function, or enum member name (ie an ID) to the + current scope + """ + if self._scope_stack[-1].get(name, False): + self._parse_error( + "Non-typedef %r previously declared as typedef " + "in this scope" % name, coord) + self._scope_stack[-1][name] = False + + def _is_type_in_scope(self, name): + """ Is *name* a typedef-name in the current scope? + """ + for scope in reversed(self._scope_stack): + # If name is an identifier in this scope it shadows typedefs in + # higher scopes. + in_scope = scope.get(name) + if in_scope is not None: return in_scope + return False + + def _lex_error_func(self, msg, line, column): + self._parse_error(msg, self._coord(line, column)) + + def _lex_on_lbrace_func(self): + self._push_scope() + + def _lex_on_rbrace_func(self): + self._pop_scope() + + def _lex_type_lookup_func(self, name): + """ Looks up types that were previously defined with + typedef. + Passed to the lexer for recognizing identifiers that + are types. + """ + is_type = self._is_type_in_scope(name) + return is_type + + def _get_yacc_lookahead_token(self): + """ We need access to yacc's lookahead token in certain cases. + This is the last token yacc requested from the lexer, so we + ask the lexer. + """ + return self.clex.last_token + + # To understand what's going on here, read sections A.8.5 and + # A.8.6 of K&R2 very carefully. + # + # A C type consists of a basic type declaration, with a list + # of modifiers. For example: + # + # int *c[5]; + # + # The basic declaration here is 'int c', and the pointer and + # the array are the modifiers. + # + # Basic declarations are represented by TypeDecl (from module c_ast) and the + # modifiers are FuncDecl, PtrDecl and ArrayDecl. + # + # The standard states that whenever a new modifier is parsed, it should be + # added to the end of the list of modifiers. For example: + # + # K&R2 A.8.6.2: Array Declarators + # + # In a declaration T D where D has the form + # D1 [constant-expression-opt] + # and the type of the identifier in the declaration T D1 is + # "type-modifier T", the type of the + # identifier of D is "type-modifier array of T" + # + # This is what this method does. The declarator it receives + # can be a list of declarators ending with TypeDecl. It + # tacks the modifier to the end of this list, just before + # the TypeDecl. + # + # Additionally, the modifier may be a list itself. This is + # useful for pointers, that can come as a chain from the rule + # p_pointer. In this case, the whole modifier list is spliced + # into the new location. + def _type_modify_decl(self, decl, modifier): + """ Tacks a type modifier on a declarator, and returns + the modified declarator. + + Note: the declarator and modifier may be modified + """ + #~ print '****' + #~ decl.show(offset=3) + #~ modifier.show(offset=3) + #~ print '****' + + modifier_head = modifier + modifier_tail = modifier + + # The modifier may be a nested list. Reach its tail. + # + while modifier_tail.type: + modifier_tail = modifier_tail.type + + # If the decl is a basic type, just tack the modifier onto + # it + # + if isinstance(decl, c_ast.TypeDecl): + modifier_tail.type = decl + return modifier + else: + # Otherwise, the decl is a list of modifiers. Reach + # its tail and splice the modifier onto the tail, + # pointing to the underlying basic type. + # + decl_tail = decl + + while not isinstance(decl_tail.type, c_ast.TypeDecl): + decl_tail = decl_tail.type + + modifier_tail.type = decl_tail.type + decl_tail.type = modifier_head + return decl + + # Due to the order in which declarators are constructed, + # they have to be fixed in order to look like a normal AST. + # + # When a declaration arrives from syntax construction, it has + # these problems: + # * The innermost TypeDecl has no type (because the basic + # type is only known at the uppermost declaration level) + # * The declaration has no variable name, since that is saved + # in the innermost TypeDecl + # * The typename of the declaration is a list of type + # specifiers, and not a node. Here, basic identifier types + # should be separated from more complex types like enums + # and structs. + # + # This method fixes these problems. + # + def _fix_decl_name_type(self, decl, typename): + """ Fixes a declaration. Modifies decl. + """ + # Reach the underlying basic type + # + type = decl + while not isinstance(type, c_ast.TypeDecl): + type = type.type + + decl.name = type.declname + type.quals = decl.quals + + # The typename is a list of types. If any type in this + # list isn't an IdentifierType, it must be the only + # type in the list (it's illegal to declare "int enum ..") + # If all the types are basic, they're collected in the + # IdentifierType holder. + # + for tn in typename: + if not isinstance(tn, c_ast.IdentifierType): + if len(typename) > 1: + self._parse_error( + "Invalid multiple types specified", tn.coord) + else: + type.type = tn + return decl + + if not typename: + # Functions default to returning int + # + if not isinstance(decl.type, c_ast.FuncDecl): + self._parse_error( + "Missing type in declaration", decl.coord) + type.type = c_ast.IdentifierType( + ['int'], + coord=decl.coord) + else: + # At this point, we know that typename is a list of IdentifierType + # nodes. Concatenate all the names into a single list. + # + type.type = c_ast.IdentifierType( + [name for id in typename for name in id.names], + coord=typename[0].coord) + return decl + + def _add_declaration_specifier(self, declspec, newspec, kind, append=False): + """ Declaration specifiers are represented by a dictionary + with the entries: + * qual: a list of type qualifiers + * storage: a list of storage type qualifiers + * type: a list of type specifiers + * function: a list of function specifiers + + This method is given a declaration specifier, and a + new specifier of a given kind. + If `append` is True, the new specifier is added to the end of + the specifiers list, otherwise it's added at the beginning. + Returns the declaration specifier, with the new + specifier incorporated. + """ + spec = declspec or dict(qual=[], storage=[], type=[], function=[]) + + if append: + spec[kind].append(newspec) + else: + spec[kind].insert(0, newspec) + + return spec + + def _build_declarations(self, spec, decls, typedef_namespace=False): + """ Builds a list of declarations all sharing the given specifiers. + If typedef_namespace is true, each declared name is added + to the "typedef namespace", which also includes objects, + functions, and enum constants. + """ + is_typedef = 'typedef' in spec['storage'] + declarations = [] + + # Bit-fields are allowed to be unnamed. + # + if decls[0].get('bitsize') is not None: + pass + + # When redeclaring typedef names as identifiers in inner scopes, a + # problem can occur where the identifier gets grouped into + # spec['type'], leaving decl as None. This can only occur for the + # first declarator. + # + elif decls[0]['decl'] is None: + if len(spec['type']) < 2 or len(spec['type'][-1].names) != 1 or \ + not self._is_type_in_scope(spec['type'][-1].names[0]): + coord = '?' + for t in spec['type']: + if hasattr(t, 'coord'): + coord = t.coord + break + self._parse_error('Invalid declaration', coord) + + # Make this look as if it came from "direct_declarator:ID" + decls[0]['decl'] = c_ast.TypeDecl( + declname=spec['type'][-1].names[0], + type=None, + quals=None, + coord=spec['type'][-1].coord) + # Remove the "new" type's name from the end of spec['type'] + del spec['type'][-1] + + # A similar problem can occur where the declaration ends up looking + # like an abstract declarator. Give it a name if this is the case. + # + elif not isinstance(decls[0]['decl'], + (c_ast.Struct, c_ast.Union, c_ast.IdentifierType)): + decls_0_tail = decls[0]['decl'] + while not isinstance(decls_0_tail, c_ast.TypeDecl): + decls_0_tail = decls_0_tail.type + if decls_0_tail.declname is None: + decls_0_tail.declname = spec['type'][-1].names[0] + del spec['type'][-1] + + for decl in decls: + assert decl['decl'] is not None + if is_typedef: + declaration = c_ast.Typedef( + name=None, + quals=spec['qual'], + storage=spec['storage'], + type=decl['decl'], + coord=decl['decl'].coord) + else: + declaration = c_ast.Decl( + name=None, + quals=spec['qual'], + storage=spec['storage'], + funcspec=spec['function'], + type=decl['decl'], + init=decl.get('init'), + bitsize=decl.get('bitsize'), + coord=decl['decl'].coord) + + if isinstance(declaration.type, + (c_ast.Struct, c_ast.Union, c_ast.IdentifierType)): + fixed_decl = declaration + else: + fixed_decl = self._fix_decl_name_type(declaration, spec['type']) + + # Add the type name defined by typedef to a + # symbol table (for usage in the lexer) + # + if typedef_namespace: + if is_typedef: + self._add_typedef_name(fixed_decl.name, fixed_decl.coord) + else: + self._add_identifier(fixed_decl.name, fixed_decl.coord) + + declarations.append(fixed_decl) + + return declarations + + def _build_function_definition(self, spec, decl, param_decls, body): + """ Builds a function definition. + """ + assert 'typedef' not in spec['storage'] + + declaration = self._build_declarations( + spec=spec, + decls=[dict(decl=decl, init=None)], + typedef_namespace=True)[0] + + return c_ast.FuncDef( + decl=declaration, + param_decls=param_decls, + body=body, + coord=decl.coord) + + def _select_struct_union_class(self, token): + """ Given a token (either STRUCT or UNION), selects the + appropriate AST class. + """ + if token == 'struct': + return c_ast.Struct + else: + return c_ast.Union + + ## + ## Precedence and associativity of operators + ## + precedence = ( + ('left', 'LOR'), + ('left', 'LAND'), + ('left', 'OR'), + ('left', 'XOR'), + ('left', 'AND'), + ('left', 'EQ', 'NE'), + ('left', 'GT', 'GE', 'LT', 'LE'), + ('left', 'RSHIFT', 'LSHIFT'), + ('left', 'PLUS', 'MINUS'), + ('left', 'TIMES', 'DIVIDE', 'MOD') + ) + + ## + ## Grammar productions + ## Implementation of the BNF defined in K&R2 A.13 + ## + + # Wrapper around a translation unit, to allow for empty input. + # Not strictly part of the C99 Grammar, but useful in practice. + # + def p_translation_unit_or_empty(self, p): + """ translation_unit_or_empty : translation_unit + | empty + """ + if p[1] is None: + p[0] = c_ast.FileAST([]) + else: + p[0] = c_ast.FileAST(p[1]) + + def p_translation_unit_1(self, p): + """ translation_unit : external_declaration + """ + # Note: external_declaration is already a list + # + p[0] = p[1] + + def p_translation_unit_2(self, p): + """ translation_unit : translation_unit external_declaration + """ + if p[2] is not None: + p[1].extend(p[2]) + p[0] = p[1] + + # Declarations always come as lists (because they can be + # several in one line), so we wrap the function definition + # into a list as well, to make the return value of + # external_declaration homogenous. + # + def p_external_declaration_1(self, p): + """ external_declaration : function_definition + """ + p[0] = [p[1]] + + def p_external_declaration_2(self, p): + """ external_declaration : declaration + """ + p[0] = p[1] + + def p_external_declaration_3(self, p): + """ external_declaration : pp_directive + | pppragma_directive + """ + p[0] = [p[1]] + + def p_external_declaration_4(self, p): + """ external_declaration : SEMI + """ + p[0] = None + + def p_pp_directive(self, p): + """ pp_directive : PPHASH + """ + self._parse_error('Directives not supported yet', + self._token_coord(p, 1)) + + def p_pppragma_directive(self, p): + """ pppragma_directive : PPPRAGMA + | PPPRAGMA PPPRAGMASTR + """ + if len(p) == 3: + p[0] = c_ast.Pragma(p[2], self._token_coord(p, 2)) + else: + p[0] = c_ast.Pragma("", self._token_coord(p, 1)) + + # In function definitions, the declarator can be followed by + # a declaration list, for old "K&R style" function definitios. + # + def p_function_definition_1(self, p): + """ function_definition : id_declarator declaration_list_opt compound_statement + """ + # no declaration specifiers - 'int' becomes the default type + spec = dict( + qual=[], + storage=[], + type=[c_ast.IdentifierType(['int'], + coord=self._token_coord(p, 1))], + function=[]) + + p[0] = self._build_function_definition( + spec=spec, + decl=p[1], + param_decls=p[2], + body=p[3]) + + def p_function_definition_2(self, p): + """ function_definition : declaration_specifiers id_declarator declaration_list_opt compound_statement + """ + spec = p[1] + + p[0] = self._build_function_definition( + spec=spec, + decl=p[2], + param_decls=p[3], + body=p[4]) + + def p_statement(self, p): + """ statement : labeled_statement + | expression_statement + | compound_statement + | selection_statement + | iteration_statement + | jump_statement + | pppragma_directive + """ + p[0] = p[1] + + # A pragma is generally considered a decorator rather than an actual statement. + # Still, for the purposes of analyzing an abstract syntax tree of C code, + # pragma's should not be ignored and were previously treated as a statement. + # This presents a problem for constructs that take a statement such as labeled_statements, + # selection_statements, and iteration_statements, causing a misleading structure + # in the AST. For example, consider the following C code. + # + # for (int i = 0; i < 3; i++) + # #pragma omp critical + # sum += 1; + # + # This code will compile and execute "sum += 1;" as the body of the for loop. + # Previous implementations of PyCParser would render the AST for this + # block of code as follows: + # + # For: + # DeclList: + # Decl: i, [], [], [] + # TypeDecl: i, [] + # IdentifierType: ['int'] + # Constant: int, 0 + # BinaryOp: < + # ID: i + # Constant: int, 3 + # UnaryOp: p++ + # ID: i + # Pragma: omp critical + # Assignment: += + # ID: sum + # Constant: int, 1 + # + # This AST misleadingly takes the Pragma as the body of the loop and the + # assignment then becomes a sibling of the loop. + # + # To solve edge cases like these, the pragmacomp_or_statement rule groups + # a pragma and its following statement (which would otherwise be orphaned) + # using a compound block, effectively turning the above code into: + # + # for (int i = 0; i < 3; i++) { + # #pragma omp critical + # sum += 1; + # } + def p_pragmacomp_or_statement(self, p): + """ pragmacomp_or_statement : pppragma_directive statement + | statement + """ + if isinstance(p[1], c_ast.Pragma) and len(p) == 3: + p[0] = c_ast.Compound( + block_items=[p[1], p[2]], + coord=self._token_coord(p, 1)) + else: + p[0] = p[1] + + # In C, declarations can come several in a line: + # int x, *px, romulo = 5; + # + # However, for the AST, we will split them to separate Decl + # nodes. + # + # This rule splits its declarations and always returns a list + # of Decl nodes, even if it's one element long. + # + def p_decl_body(self, p): + """ decl_body : declaration_specifiers init_declarator_list_opt + | declaration_specifiers_no_type id_init_declarator_list_opt + """ + spec = p[1] + + # p[2] (init_declarator_list_opt) is either a list or None + # + if p[2] is None: + # By the standard, you must have at least one declarator unless + # declaring a structure tag, a union tag, or the members of an + # enumeration. + # + ty = spec['type'] + s_u_or_e = (c_ast.Struct, c_ast.Union, c_ast.Enum) + if len(ty) == 1 and isinstance(ty[0], s_u_or_e): + decls = [c_ast.Decl( + name=None, + quals=spec['qual'], + storage=spec['storage'], + funcspec=spec['function'], + type=ty[0], + init=None, + bitsize=None, + coord=ty[0].coord)] + + # However, this case can also occur on redeclared identifiers in + # an inner scope. The trouble is that the redeclared type's name + # gets grouped into declaration_specifiers; _build_declarations + # compensates for this. + # + else: + decls = self._build_declarations( + spec=spec, + decls=[dict(decl=None, init=None)], + typedef_namespace=True) + + else: + decls = self._build_declarations( + spec=spec, + decls=p[2], + typedef_namespace=True) + + p[0] = decls + + # The declaration has been split to a decl_body sub-rule and + # SEMI, because having them in a single rule created a problem + # for defining typedefs. + # + # If a typedef line was directly followed by a line using the + # type defined with the typedef, the type would not be + # recognized. This is because to reduce the declaration rule, + # the parser's lookahead asked for the token after SEMI, which + # was the type from the next line, and the lexer had no chance + # to see the updated type symbol table. + # + # Splitting solves this problem, because after seeing SEMI, + # the parser reduces decl_body, which actually adds the new + # type into the table to be seen by the lexer before the next + # line is reached. + def p_declaration(self, p): + """ declaration : decl_body SEMI + """ + p[0] = p[1] + + # Since each declaration is a list of declarations, this + # rule will combine all the declarations and return a single + # list + # + def p_declaration_list(self, p): + """ declaration_list : declaration + | declaration_list declaration + """ + p[0] = p[1] if len(p) == 2 else p[1] + p[2] + + # To know when declaration-specifiers end and declarators begin, + # we require declaration-specifiers to have at least one + # type-specifier, and disallow typedef-names after we've seen any + # type-specifier. These are both required by the spec. + # + def p_declaration_specifiers_no_type_1(self, p): + """ declaration_specifiers_no_type : type_qualifier declaration_specifiers_no_type_opt + """ + p[0] = self._add_declaration_specifier(p[2], p[1], 'qual') + + def p_declaration_specifiers_no_type_2(self, p): + """ declaration_specifiers_no_type : storage_class_specifier declaration_specifiers_no_type_opt + """ + p[0] = self._add_declaration_specifier(p[2], p[1], 'storage') + + def p_declaration_specifiers_no_type_3(self, p): + """ declaration_specifiers_no_type : function_specifier declaration_specifiers_no_type_opt + """ + p[0] = self._add_declaration_specifier(p[2], p[1], 'function') + + + def p_declaration_specifiers_1(self, p): + """ declaration_specifiers : declaration_specifiers type_qualifier + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'qual', append=True) + + def p_declaration_specifiers_2(self, p): + """ declaration_specifiers : declaration_specifiers storage_class_specifier + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'storage', append=True) + + def p_declaration_specifiers_3(self, p): + """ declaration_specifiers : declaration_specifiers function_specifier + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'function', append=True) + + def p_declaration_specifiers_4(self, p): + """ declaration_specifiers : declaration_specifiers type_specifier_no_typeid + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'type', append=True) + + def p_declaration_specifiers_5(self, p): + """ declaration_specifiers : type_specifier + """ + p[0] = self._add_declaration_specifier(None, p[1], 'type') + + def p_declaration_specifiers_6(self, p): + """ declaration_specifiers : declaration_specifiers_no_type type_specifier + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'type', append=True) + + + def p_storage_class_specifier(self, p): + """ storage_class_specifier : AUTO + | REGISTER + | STATIC + | EXTERN + | TYPEDEF + """ + p[0] = p[1] + + def p_function_specifier(self, p): + """ function_specifier : INLINE + """ + p[0] = p[1] + + def p_type_specifier_no_typeid(self, p): + """ type_specifier_no_typeid : VOID + | _BOOL + | CHAR + | SHORT + | INT + | LONG + | FLOAT + | DOUBLE + | _COMPLEX + | SIGNED + | UNSIGNED + | __INT128 + """ + p[0] = c_ast.IdentifierType([p[1]], coord=self._token_coord(p, 1)) + + def p_type_specifier(self, p): + """ type_specifier : typedef_name + | enum_specifier + | struct_or_union_specifier + | type_specifier_no_typeid + """ + p[0] = p[1] + + def p_type_qualifier(self, p): + """ type_qualifier : CONST + | RESTRICT + | VOLATILE + """ + p[0] = p[1] + + def p_init_declarator_list(self, p): + """ init_declarator_list : init_declarator + | init_declarator_list COMMA init_declarator + """ + p[0] = p[1] + [p[3]] if len(p) == 4 else [p[1]] + + # Returns a {decl=<declarator> : init=<initializer>} dictionary + # If there's no initializer, uses None + # + def p_init_declarator(self, p): + """ init_declarator : declarator + | declarator EQUALS initializer + """ + p[0] = dict(decl=p[1], init=(p[3] if len(p) > 2 else None)) + + def p_id_init_declarator_list(self, p): + """ id_init_declarator_list : id_init_declarator + | id_init_declarator_list COMMA init_declarator + """ + p[0] = p[1] + [p[3]] if len(p) == 4 else [p[1]] + + def p_id_init_declarator(self, p): + """ id_init_declarator : id_declarator + | id_declarator EQUALS initializer + """ + p[0] = dict(decl=p[1], init=(p[3] if len(p) > 2 else None)) + + # Require at least one type specifier in a specifier-qualifier-list + # + def p_specifier_qualifier_list_1(self, p): + """ specifier_qualifier_list : specifier_qualifier_list type_specifier_no_typeid + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'type', append=True) + + def p_specifier_qualifier_list_2(self, p): + """ specifier_qualifier_list : specifier_qualifier_list type_qualifier + """ + p[0] = self._add_declaration_specifier(p[1], p[2], 'qual', append=True) + + def p_specifier_qualifier_list_3(self, p): + """ specifier_qualifier_list : type_specifier + """ + p[0] = self._add_declaration_specifier(None, p[1], 'type') + + def p_specifier_qualifier_list_4(self, p): + """ specifier_qualifier_list : type_qualifier_list type_specifier + """ + spec = dict(qual=p[1], storage=[], type=[], function=[]) + p[0] = self._add_declaration_specifier(spec, p[2], 'type', append=True) + + # TYPEID is allowed here (and in other struct/enum related tag names), because + # struct/enum tags reside in their own namespace and can be named the same as types + # + def p_struct_or_union_specifier_1(self, p): + """ struct_or_union_specifier : struct_or_union ID + | struct_or_union TYPEID + """ + klass = self._select_struct_union_class(p[1]) + # None means no list of members + p[0] = klass( + name=p[2], + decls=None, + coord=self._token_coord(p, 2)) + + def p_struct_or_union_specifier_2(self, p): + """ struct_or_union_specifier : struct_or_union brace_open struct_declaration_list brace_close + | struct_or_union brace_open brace_close + """ + klass = self._select_struct_union_class(p[1]) + if len(p) == 4: + # Empty sequence means an empty list of members + p[0] = klass( + name=None, + decls=[], + coord=self._token_coord(p, 2)) + else: + p[0] = klass( + name=None, + decls=p[3], + coord=self._token_coord(p, 2)) + + + def p_struct_or_union_specifier_3(self, p): + """ struct_or_union_specifier : struct_or_union ID brace_open struct_declaration_list brace_close + | struct_or_union ID brace_open brace_close + | struct_or_union TYPEID brace_open struct_declaration_list brace_close + | struct_or_union TYPEID brace_open brace_close + """ + klass = self._select_struct_union_class(p[1]) + if len(p) == 5: + # Empty sequence means an empty list of members + p[0] = klass( + name=p[2], + decls=[], + coord=self._token_coord(p, 2)) + else: + p[0] = klass( + name=p[2], + decls=p[4], + coord=self._token_coord(p, 2)) + + def p_struct_or_union(self, p): + """ struct_or_union : STRUCT + | UNION + """ + p[0] = p[1] + + # Combine all declarations into a single list + # + def p_struct_declaration_list(self, p): + """ struct_declaration_list : struct_declaration + | struct_declaration_list struct_declaration + """ + if len(p) == 2: + p[0] = p[1] or [] + else: + p[0] = p[1] + (p[2] or []) + + def p_struct_declaration_1(self, p): + """ struct_declaration : specifier_qualifier_list struct_declarator_list_opt SEMI + """ + spec = p[1] + assert 'typedef' not in spec['storage'] + + if p[2] is not None: + decls = self._build_declarations( + spec=spec, + decls=p[2]) + + elif len(spec['type']) == 1: + # Anonymous struct/union, gcc extension, C1x feature. + # Although the standard only allows structs/unions here, I see no + # reason to disallow other types since some compilers have typedefs + # here, and pycparser isn't about rejecting all invalid code. + # + node = spec['type'][0] + if isinstance(node, c_ast.Node): + decl_type = node + else: + decl_type = c_ast.IdentifierType(node) + + decls = self._build_declarations( + spec=spec, + decls=[dict(decl=decl_type)]) + + else: + # Structure/union members can have the same names as typedefs. + # The trouble is that the member's name gets grouped into + # specifier_qualifier_list; _build_declarations compensates. + # + decls = self._build_declarations( + spec=spec, + decls=[dict(decl=None, init=None)]) + + p[0] = decls + + def p_struct_declaration_2(self, p): + """ struct_declaration : SEMI + """ + p[0] = None + + def p_struct_declaration_3(self, p): + """ struct_declaration : pppragma_directive + """ + p[0] = [p[1]] + + def p_struct_declarator_list(self, p): + """ struct_declarator_list : struct_declarator + | struct_declarator_list COMMA struct_declarator + """ + p[0] = p[1] + [p[3]] if len(p) == 4 else [p[1]] + + # struct_declarator passes up a dict with the keys: decl (for + # the underlying declarator) and bitsize (for the bitsize) + # + def p_struct_declarator_1(self, p): + """ struct_declarator : declarator + """ + p[0] = {'decl': p[1], 'bitsize': None} + + def p_struct_declarator_2(self, p): + """ struct_declarator : declarator COLON constant_expression + | COLON constant_expression + """ + if len(p) > 3: + p[0] = {'decl': p[1], 'bitsize': p[3]} + else: + p[0] = {'decl': c_ast.TypeDecl(None, None, None), 'bitsize': p[2]} + + def p_enum_specifier_1(self, p): + """ enum_specifier : ENUM ID + | ENUM TYPEID + """ + p[0] = c_ast.Enum(p[2], None, self._token_coord(p, 1)) + + def p_enum_specifier_2(self, p): + """ enum_specifier : ENUM brace_open enumerator_list brace_close + """ + p[0] = c_ast.Enum(None, p[3], self._token_coord(p, 1)) + + def p_enum_specifier_3(self, p): + """ enum_specifier : ENUM ID brace_open enumerator_list brace_close + | ENUM TYPEID brace_open enumerator_list brace_close + """ + p[0] = c_ast.Enum(p[2], p[4], self._token_coord(p, 1)) + + def p_enumerator_list(self, p): + """ enumerator_list : enumerator + | enumerator_list COMMA + | enumerator_list COMMA enumerator + """ + if len(p) == 2: + p[0] = c_ast.EnumeratorList([p[1]], p[1].coord) + elif len(p) == 3: + p[0] = p[1] + else: + p[1].enumerators.append(p[3]) + p[0] = p[1] + + def p_enumerator(self, p): + """ enumerator : ID + | ID EQUALS constant_expression + """ + if len(p) == 2: + enumerator = c_ast.Enumerator( + p[1], None, + self._token_coord(p, 1)) + else: + enumerator = c_ast.Enumerator( + p[1], p[3], + self._token_coord(p, 1)) + self._add_identifier(enumerator.name, enumerator.coord) + + p[0] = enumerator + + def p_declarator(self, p): + """ declarator : id_declarator + | typeid_declarator + """ + p[0] = p[1] + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_xxx_declarator_1(self, p): + """ xxx_declarator : direct_xxx_declarator + """ + p[0] = p[1] + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_xxx_declarator_2(self, p): + """ xxx_declarator : pointer direct_xxx_declarator + """ + p[0] = self._type_modify_decl(p[2], p[1]) + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_direct_xxx_declarator_1(self, p): + """ direct_xxx_declarator : yyy + """ + p[0] = c_ast.TypeDecl( + declname=p[1], + type=None, + quals=None, + coord=self._token_coord(p, 1)) + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID')) + def p_direct_xxx_declarator_2(self, p): + """ direct_xxx_declarator : LPAREN xxx_declarator RPAREN + """ + p[0] = p[2] + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_direct_xxx_declarator_3(self, p): + """ direct_xxx_declarator : direct_xxx_declarator LBRACKET type_qualifier_list_opt assignment_expression_opt RBRACKET + """ + quals = (p[3] if len(p) > 5 else []) or [] + # Accept dimension qualifiers + # Per C99 6.7.5.3 p7 + arr = c_ast.ArrayDecl( + type=None, + dim=p[4] if len(p) > 5 else p[3], + dim_quals=quals, + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=arr) + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_direct_xxx_declarator_4(self, p): + """ direct_xxx_declarator : direct_xxx_declarator LBRACKET STATIC type_qualifier_list_opt assignment_expression RBRACKET + | direct_xxx_declarator LBRACKET type_qualifier_list STATIC assignment_expression RBRACKET + """ + # Using slice notation for PLY objects doesn't work in Python 3 for the + # version of PLY embedded with pycparser; see PLY Google Code issue 30. + # Work around that here by listing the two elements separately. + listed_quals = [item if isinstance(item, list) else [item] + for item in [p[3],p[4]]] + dim_quals = [qual for sublist in listed_quals for qual in sublist + if qual is not None] + arr = c_ast.ArrayDecl( + type=None, + dim=p[5], + dim_quals=dim_quals, + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=arr) + + # Special for VLAs + # + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_direct_xxx_declarator_5(self, p): + """ direct_xxx_declarator : direct_xxx_declarator LBRACKET type_qualifier_list_opt TIMES RBRACKET + """ + arr = c_ast.ArrayDecl( + type=None, + dim=c_ast.ID(p[4], self._token_coord(p, 4)), + dim_quals=p[3] if p[3] != None else [], + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=arr) + + @parameterized(('id', 'ID'), ('typeid', 'TYPEID'), ('typeid_noparen', 'TYPEID')) + def p_direct_xxx_declarator_6(self, p): + """ direct_xxx_declarator : direct_xxx_declarator LPAREN parameter_type_list RPAREN + | direct_xxx_declarator LPAREN identifier_list_opt RPAREN + """ + func = c_ast.FuncDecl( + args=p[3], + type=None, + coord=p[1].coord) + + # To see why _get_yacc_lookahead_token is needed, consider: + # typedef char TT; + # void foo(int TT) { TT = 10; } + # Outside the function, TT is a typedef, but inside (starting and + # ending with the braces) it's a parameter. The trouble begins with + # yacc's lookahead token. We don't know if we're declaring or + # defining a function until we see LBRACE, but if we wait for yacc to + # trigger a rule on that token, then TT will have already been read + # and incorrectly interpreted as TYPEID. We need to add the + # parameters to the scope the moment the lexer sees LBRACE. + # + if self._get_yacc_lookahead_token().type == "LBRACE": + if func.args is not None: + for param in func.args.params: + if isinstance(param, c_ast.EllipsisParam): break + self._add_identifier(param.name, param.coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=func) + + def p_pointer(self, p): + """ pointer : TIMES type_qualifier_list_opt + | TIMES type_qualifier_list_opt pointer + """ + coord = self._token_coord(p, 1) + # 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" + # + # So when we construct PtrDecl nestings, the leftmost pointer goes in + # as the most nested type. + nested_type = c_ast.PtrDecl(quals=p[2] or [], type=None, coord=coord) + if len(p) > 3: + tail_type = p[3] + while tail_type.type is not None: + tail_type = tail_type.type + tail_type.type = nested_type + p[0] = p[3] + else: + p[0] = nested_type + + def p_type_qualifier_list(self, p): + """ type_qualifier_list : type_qualifier + | type_qualifier_list type_qualifier + """ + p[0] = [p[1]] if len(p) == 2 else p[1] + [p[2]] + + def p_parameter_type_list(self, p): + """ parameter_type_list : parameter_list + | parameter_list COMMA ELLIPSIS + """ + if len(p) > 2: + p[1].params.append(c_ast.EllipsisParam(self._token_coord(p, 3))) + + p[0] = p[1] + + def p_parameter_list(self, p): + """ parameter_list : parameter_declaration + | parameter_list COMMA parameter_declaration + """ + if len(p) == 2: # single parameter + p[0] = c_ast.ParamList([p[1]], p[1].coord) + else: + p[1].params.append(p[3]) + p[0] = p[1] + + # From ISO/IEC 9899:TC2, 6.7.5.3.11: + # "If, in a parameter declaration, an identifier can be treated either + # as a typedef name or as a parameter name, it shall be taken as a + # typedef name." + # + # Inside a parameter declaration, once we've reduced declaration specifiers, + # if we shift in an LPAREN and see a TYPEID, it could be either an abstract + # declarator or a declarator nested inside parens. This rule tells us to + # always treat it as an abstract declarator. Therefore, we only accept + # `id_declarator`s and `typeid_noparen_declarator`s. + def p_parameter_declaration_1(self, p): + """ parameter_declaration : declaration_specifiers id_declarator + | declaration_specifiers typeid_noparen_declarator + """ + spec = p[1] + if not spec['type']: + spec['type'] = [c_ast.IdentifierType(['int'], + coord=self._token_coord(p, 1))] + p[0] = self._build_declarations( + spec=spec, + decls=[dict(decl=p[2])])[0] + + def p_parameter_declaration_2(self, p): + """ parameter_declaration : declaration_specifiers abstract_declarator_opt + """ + spec = p[1] + if not spec['type']: + spec['type'] = [c_ast.IdentifierType(['int'], + coord=self._token_coord(p, 1))] + + # Parameters can have the same names as typedefs. The trouble is that + # the parameter's name gets grouped into declaration_specifiers, making + # it look like an old-style declaration; compensate. + # + if len(spec['type']) > 1 and len(spec['type'][-1].names) == 1 and \ + self._is_type_in_scope(spec['type'][-1].names[0]): + decl = self._build_declarations( + spec=spec, + decls=[dict(decl=p[2], init=None)])[0] + + # This truly is an old-style parameter declaration + # + else: + decl = c_ast.Typename( + name='', + quals=spec['qual'], + type=p[2] or c_ast.TypeDecl(None, None, None), + coord=self._token_coord(p, 2)) + typename = spec['type'] + decl = self._fix_decl_name_type(decl, typename) + + p[0] = decl + + def p_identifier_list(self, p): + """ identifier_list : identifier + | identifier_list COMMA identifier + """ + if len(p) == 2: # single parameter + p[0] = c_ast.ParamList([p[1]], p[1].coord) + else: + p[1].params.append(p[3]) + p[0] = p[1] + + def p_initializer_1(self, p): + """ initializer : assignment_expression + """ + p[0] = p[1] + + def p_initializer_2(self, p): + """ initializer : brace_open initializer_list_opt brace_close + | brace_open initializer_list COMMA brace_close + """ + if p[2] is None: + p[0] = c_ast.InitList([], self._token_coord(p, 1)) + else: + p[0] = p[2] + + def p_initializer_list(self, p): + """ initializer_list : designation_opt initializer + | initializer_list COMMA designation_opt initializer + """ + if len(p) == 3: # single initializer + init = p[2] if p[1] is None else c_ast.NamedInitializer(p[1], p[2]) + p[0] = c_ast.InitList([init], p[2].coord) + else: + init = p[4] if p[3] is None else c_ast.NamedInitializer(p[3], p[4]) + p[1].exprs.append(init) + p[0] = p[1] + + def p_designation(self, p): + """ designation : designator_list EQUALS + """ + p[0] = p[1] + + # Designators are represented as a list of nodes, in the order in which + # they're written in the code. + # + def p_designator_list(self, p): + """ designator_list : designator + | designator_list designator + """ + p[0] = [p[1]] if len(p) == 2 else p[1] + [p[2]] + + def p_designator(self, p): + """ designator : LBRACKET constant_expression RBRACKET + | PERIOD identifier + """ + p[0] = p[2] + + def p_type_name(self, p): + """ type_name : specifier_qualifier_list abstract_declarator_opt + """ + typename = c_ast.Typename( + name='', + quals=p[1]['qual'], + type=p[2] or c_ast.TypeDecl(None, None, None), + coord=self._token_coord(p, 2)) + + p[0] = self._fix_decl_name_type(typename, p[1]['type']) + + def p_abstract_declarator_1(self, p): + """ abstract_declarator : pointer + """ + dummytype = c_ast.TypeDecl(None, None, None) + p[0] = self._type_modify_decl( + decl=dummytype, + modifier=p[1]) + + def p_abstract_declarator_2(self, p): + """ abstract_declarator : pointer direct_abstract_declarator + """ + p[0] = self._type_modify_decl(p[2], p[1]) + + def p_abstract_declarator_3(self, p): + """ abstract_declarator : direct_abstract_declarator + """ + p[0] = p[1] + + # Creating and using direct_abstract_declarator_opt here + # instead of listing both direct_abstract_declarator and the + # lack of it in the beginning of _1 and _2 caused two + # shift/reduce errors. + # + def p_direct_abstract_declarator_1(self, p): + """ direct_abstract_declarator : LPAREN abstract_declarator RPAREN """ + p[0] = p[2] + + def p_direct_abstract_declarator_2(self, p): + """ direct_abstract_declarator : direct_abstract_declarator LBRACKET assignment_expression_opt RBRACKET + """ + arr = c_ast.ArrayDecl( + type=None, + dim=p[3], + dim_quals=[], + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=arr) + + def p_direct_abstract_declarator_3(self, p): + """ direct_abstract_declarator : LBRACKET assignment_expression_opt RBRACKET + """ + p[0] = c_ast.ArrayDecl( + type=c_ast.TypeDecl(None, None, None), + dim=p[2], + dim_quals=[], + coord=self._token_coord(p, 1)) + + def p_direct_abstract_declarator_4(self, p): + """ direct_abstract_declarator : direct_abstract_declarator LBRACKET TIMES RBRACKET + """ + arr = c_ast.ArrayDecl( + type=None, + dim=c_ast.ID(p[3], self._token_coord(p, 3)), + dim_quals=[], + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=arr) + + def p_direct_abstract_declarator_5(self, p): + """ direct_abstract_declarator : LBRACKET TIMES RBRACKET + """ + p[0] = c_ast.ArrayDecl( + type=c_ast.TypeDecl(None, None, None), + dim=c_ast.ID(p[3], self._token_coord(p, 3)), + dim_quals=[], + coord=self._token_coord(p, 1)) + + def p_direct_abstract_declarator_6(self, p): + """ direct_abstract_declarator : direct_abstract_declarator LPAREN parameter_type_list_opt RPAREN + """ + func = c_ast.FuncDecl( + args=p[3], + type=None, + coord=p[1].coord) + + p[0] = self._type_modify_decl(decl=p[1], modifier=func) + + def p_direct_abstract_declarator_7(self, p): + """ direct_abstract_declarator : LPAREN parameter_type_list_opt RPAREN + """ + p[0] = c_ast.FuncDecl( + args=p[2], + type=c_ast.TypeDecl(None, None, None), + coord=self._token_coord(p, 1)) + + # declaration is a list, statement isn't. To make it consistent, block_item + # will always be a list + # + def p_block_item(self, p): + """ block_item : declaration + | statement + """ + p[0] = p[1] if isinstance(p[1], list) else [p[1]] + + # Since we made block_item a list, this just combines lists + # + def p_block_item_list(self, p): + """ block_item_list : block_item + | block_item_list block_item + """ + # Empty block items (plain ';') produce [None], so ignore them + p[0] = p[1] if (len(p) == 2 or p[2] == [None]) else p[1] + p[2] + + def p_compound_statement_1(self, p): + """ compound_statement : brace_open block_item_list_opt brace_close """ + p[0] = c_ast.Compound( + block_items=p[2], + coord=self._token_coord(p, 1)) + + def p_labeled_statement_1(self, p): + """ labeled_statement : ID COLON pragmacomp_or_statement """ + p[0] = c_ast.Label(p[1], p[3], self._token_coord(p, 1)) + + def p_labeled_statement_2(self, p): + """ labeled_statement : CASE constant_expression COLON pragmacomp_or_statement """ + p[0] = c_ast.Case(p[2], [p[4]], self._token_coord(p, 1)) + + def p_labeled_statement_3(self, p): + """ labeled_statement : DEFAULT COLON pragmacomp_or_statement """ + p[0] = c_ast.Default([p[3]], self._token_coord(p, 1)) + + def p_selection_statement_1(self, p): + """ selection_statement : IF LPAREN expression RPAREN pragmacomp_or_statement """ + p[0] = c_ast.If(p[3], p[5], None, self._token_coord(p, 1)) + + def p_selection_statement_2(self, p): + """ selection_statement : IF LPAREN expression RPAREN statement ELSE pragmacomp_or_statement """ + p[0] = c_ast.If(p[3], p[5], p[7], self._token_coord(p, 1)) + + def p_selection_statement_3(self, p): + """ selection_statement : SWITCH LPAREN expression RPAREN pragmacomp_or_statement """ + p[0] = fix_switch_cases( + c_ast.Switch(p[3], p[5], self._token_coord(p, 1))) + + def p_iteration_statement_1(self, p): + """ iteration_statement : WHILE LPAREN expression RPAREN pragmacomp_or_statement """ + p[0] = c_ast.While(p[3], p[5], self._token_coord(p, 1)) + + def p_iteration_statement_2(self, p): + """ iteration_statement : DO pragmacomp_or_statement WHILE LPAREN expression RPAREN SEMI """ + p[0] = c_ast.DoWhile(p[5], p[2], self._token_coord(p, 1)) + + def p_iteration_statement_3(self, p): + """ iteration_statement : FOR LPAREN expression_opt SEMI expression_opt SEMI expression_opt RPAREN pragmacomp_or_statement """ + p[0] = c_ast.For(p[3], p[5], p[7], p[9], self._token_coord(p, 1)) + + def p_iteration_statement_4(self, p): + """ iteration_statement : FOR LPAREN declaration expression_opt SEMI expression_opt RPAREN pragmacomp_or_statement """ + p[0] = c_ast.For(c_ast.DeclList(p[3], self._token_coord(p, 1)), + p[4], p[6], p[8], self._token_coord(p, 1)) + + def p_jump_statement_1(self, p): + """ jump_statement : GOTO ID SEMI """ + p[0] = c_ast.Goto(p[2], self._token_coord(p, 1)) + + def p_jump_statement_2(self, p): + """ jump_statement : BREAK SEMI """ + p[0] = c_ast.Break(self._token_coord(p, 1)) + + def p_jump_statement_3(self, p): + """ jump_statement : CONTINUE SEMI """ + p[0] = c_ast.Continue(self._token_coord(p, 1)) + + def p_jump_statement_4(self, p): + """ jump_statement : RETURN expression SEMI + | RETURN SEMI + """ + p[0] = c_ast.Return(p[2] if len(p) == 4 else None, self._token_coord(p, 1)) + + def p_expression_statement(self, p): + """ expression_statement : expression_opt SEMI """ + if p[1] is None: + p[0] = c_ast.EmptyStatement(self._token_coord(p, 2)) + else: + p[0] = p[1] + + def p_expression(self, p): + """ expression : assignment_expression + | expression COMMA assignment_expression + """ + if len(p) == 2: + p[0] = p[1] + else: + if not isinstance(p[1], c_ast.ExprList): + p[1] = c_ast.ExprList([p[1]], p[1].coord) + + p[1].exprs.append(p[3]) + p[0] = p[1] + + def p_typedef_name(self, p): + """ typedef_name : TYPEID """ + p[0] = c_ast.IdentifierType([p[1]], coord=self._token_coord(p, 1)) + + def p_assignment_expression(self, p): + """ assignment_expression : conditional_expression + | unary_expression assignment_operator assignment_expression + """ + if len(p) == 2: + p[0] = p[1] + else: + p[0] = c_ast.Assignment(p[2], p[1], p[3], p[1].coord) + + # K&R2 defines these as many separate rules, to encode + # precedence and associativity. Why work hard ? I'll just use + # the built in precedence/associativity specification feature + # of PLY. (see precedence declaration above) + # + def p_assignment_operator(self, p): + """ assignment_operator : EQUALS + | XOREQUAL + | TIMESEQUAL + | DIVEQUAL + | MODEQUAL + | PLUSEQUAL + | MINUSEQUAL + | LSHIFTEQUAL + | RSHIFTEQUAL + | ANDEQUAL + | OREQUAL + """ + p[0] = p[1] + + def p_constant_expression(self, p): + """ constant_expression : conditional_expression """ + p[0] = p[1] + + def p_conditional_expression(self, p): + """ conditional_expression : binary_expression + | binary_expression CONDOP expression COLON conditional_expression + """ + if len(p) == 2: + p[0] = p[1] + else: + p[0] = c_ast.TernaryOp(p[1], p[3], p[5], p[1].coord) + + def p_binary_expression(self, p): + """ binary_expression : cast_expression + | binary_expression TIMES binary_expression + | binary_expression DIVIDE binary_expression + | binary_expression MOD binary_expression + | binary_expression PLUS binary_expression + | binary_expression MINUS binary_expression + | binary_expression RSHIFT binary_expression + | binary_expression LSHIFT binary_expression + | binary_expression LT binary_expression + | binary_expression LE binary_expression + | binary_expression GE binary_expression + | binary_expression GT binary_expression + | binary_expression EQ binary_expression + | binary_expression NE binary_expression + | binary_expression AND binary_expression + | binary_expression OR binary_expression + | binary_expression XOR binary_expression + | binary_expression LAND binary_expression + | binary_expression LOR binary_expression + """ + if len(p) == 2: + p[0] = p[1] + else: + p[0] = c_ast.BinaryOp(p[2], p[1], p[3], p[1].coord) + + def p_cast_expression_1(self, p): + """ cast_expression : unary_expression """ + p[0] = p[1] + + def p_cast_expression_2(self, p): + """ cast_expression : LPAREN type_name RPAREN cast_expression """ + p[0] = c_ast.Cast(p[2], p[4], self._token_coord(p, 1)) + + def p_unary_expression_1(self, p): + """ unary_expression : postfix_expression """ + p[0] = p[1] + + def p_unary_expression_2(self, p): + """ unary_expression : PLUSPLUS unary_expression + | MINUSMINUS unary_expression + | unary_operator cast_expression + """ + p[0] = c_ast.UnaryOp(p[1], p[2], p[2].coord) + + def p_unary_expression_3(self, p): + """ unary_expression : SIZEOF unary_expression + | SIZEOF LPAREN type_name RPAREN + """ + p[0] = c_ast.UnaryOp( + p[1], + p[2] if len(p) == 3 else p[3], + self._token_coord(p, 1)) + + def p_unary_operator(self, p): + """ unary_operator : AND + | TIMES + | PLUS + | MINUS + | NOT + | LNOT + """ + p[0] = p[1] + + def p_postfix_expression_1(self, p): + """ postfix_expression : primary_expression """ + p[0] = p[1] + + def p_postfix_expression_2(self, p): + """ postfix_expression : postfix_expression LBRACKET expression RBRACKET """ + p[0] = c_ast.ArrayRef(p[1], p[3], p[1].coord) + + def p_postfix_expression_3(self, p): + """ postfix_expression : postfix_expression LPAREN argument_expression_list RPAREN + | postfix_expression LPAREN RPAREN + """ + p[0] = c_ast.FuncCall(p[1], p[3] if len(p) == 5 else None, p[1].coord) + + def p_postfix_expression_4(self, p): + """ postfix_expression : postfix_expression PERIOD ID + | postfix_expression PERIOD TYPEID + | postfix_expression ARROW ID + | postfix_expression ARROW TYPEID + """ + field = c_ast.ID(p[3], self._token_coord(p, 3)) + p[0] = c_ast.StructRef(p[1], p[2], field, p[1].coord) + + def p_postfix_expression_5(self, p): + """ postfix_expression : postfix_expression PLUSPLUS + | postfix_expression MINUSMINUS + """ + p[0] = c_ast.UnaryOp('p' + p[2], p[1], p[1].coord) + + def p_postfix_expression_6(self, p): + """ postfix_expression : LPAREN type_name RPAREN brace_open initializer_list brace_close + | LPAREN type_name RPAREN brace_open initializer_list COMMA brace_close + """ + p[0] = c_ast.CompoundLiteral(p[2], p[5]) + + def p_primary_expression_1(self, p): + """ primary_expression : identifier """ + p[0] = p[1] + + def p_primary_expression_2(self, p): + """ primary_expression : constant """ + p[0] = p[1] + + def p_primary_expression_3(self, p): + """ primary_expression : unified_string_literal + | unified_wstring_literal + """ + p[0] = p[1] + + def p_primary_expression_4(self, p): + """ primary_expression : LPAREN expression RPAREN """ + p[0] = p[2] + + def p_primary_expression_5(self, p): + """ primary_expression : OFFSETOF LPAREN type_name COMMA offsetof_member_designator RPAREN + """ + coord = self._token_coord(p, 1) + p[0] = c_ast.FuncCall(c_ast.ID(p[1], coord), + c_ast.ExprList([p[3], p[5]], coord), + coord) + + def p_offsetof_member_designator(self, p): + """ offsetof_member_designator : identifier + | offsetof_member_designator PERIOD identifier + | offsetof_member_designator LBRACKET expression RBRACKET + """ + if len(p) == 2: + p[0] = p[1] + elif len(p) == 4: + field = c_ast.ID(p[3], self._token_coord(p, 3)) + p[0] = c_ast.StructRef(p[1], p[2], field, p[1].coord) + elif len(p) == 5: + p[0] = c_ast.ArrayRef(p[1], p[3], p[1].coord) + else: + raise NotImplementedError("Unexpected parsing state. len(p): %u" % len(p)) + + def p_argument_expression_list(self, p): + """ argument_expression_list : assignment_expression + | argument_expression_list COMMA assignment_expression + """ + if len(p) == 2: # single expr + p[0] = c_ast.ExprList([p[1]], p[1].coord) + else: + p[1].exprs.append(p[3]) + p[0] = p[1] + + def p_identifier(self, p): + """ identifier : ID """ + p[0] = c_ast.ID(p[1], self._token_coord(p, 1)) + + def p_constant_1(self, p): + """ constant : INT_CONST_DEC + | INT_CONST_OCT + | INT_CONST_HEX + | INT_CONST_BIN + """ + p[0] = c_ast.Constant( + 'int', p[1], self._token_coord(p, 1)) + + def p_constant_2(self, p): + """ constant : FLOAT_CONST + | HEX_FLOAT_CONST + """ + if 'x' in p[1].lower(): + t = 'float' + else: + if p[1][-1] in ('f', 'F'): + t = 'float' + elif p[1][-1] in ('l', 'L'): + t = 'long double' + else: + t = 'double' + + p[0] = c_ast.Constant( + t, p[1], self._token_coord(p, 1)) + + def p_constant_3(self, p): + """ constant : CHAR_CONST + | WCHAR_CONST + """ + p[0] = c_ast.Constant( + 'char', p[1], self._token_coord(p, 1)) + + # The "unified" string and wstring literal rules are for supporting + # concatenation of adjacent string literals. + # I.e. "hello " "world" is seen by the C compiler as a single string literal + # with the value "hello world" + # + def p_unified_string_literal(self, p): + """ unified_string_literal : STRING_LITERAL + | unified_string_literal STRING_LITERAL + """ + if len(p) == 2: # single literal + p[0] = c_ast.Constant( + 'string', p[1], self._token_coord(p, 1)) + else: + p[1].value = p[1].value[:-1] + p[2][1:] + p[0] = p[1] + + def p_unified_wstring_literal(self, p): + """ unified_wstring_literal : WSTRING_LITERAL + | unified_wstring_literal WSTRING_LITERAL + """ + if len(p) == 2: # single literal + p[0] = c_ast.Constant( + 'string', p[1], self._token_coord(p, 1)) + else: + p[1].value = p[1].value.rstrip()[:-1] + p[2][2:] + p[0] = p[1] + + def p_brace_open(self, p): + """ brace_open : LBRACE + """ + p[0] = p[1] + p.set_lineno(0, p.lineno(1)) + + def p_brace_close(self, p): + """ brace_close : RBRACE + """ + p[0] = p[1] + p.set_lineno(0, p.lineno(1)) + + def p_empty(self, p): + 'empty : ' + p[0] = None + + def p_error(self, p): + # If error recovery is added here in the future, make sure + # _get_yacc_lookahead_token still works! + # + if p: + self._parse_error( + 'before: %s' % p.value, + self._coord(lineno=p.lineno, + column=self.clex.find_tok_column(p))) + else: + self._parse_error('At end of input', self.clex.filename) diff --git a/pycparser/ply/LICENSE b/pycparser/ply/LICENSE new file mode 100644 index 0000000..bac0d9a --- /dev/null +++ b/pycparser/ply/LICENSE @@ -0,0 +1,34 @@ +PLY (Python Lex-Yacc) Version 3.10 + +Copyright (C) 2001-2017 +David M. Beazley (Dabeaz LLC) +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the 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. + +More information about PLY can be obtained on the PLY webpage at: + + http://www.dabeaz.com/ply diff --git a/pycparser/ply/__init__.py b/pycparser/ply/__init__.py new file mode 100644 index 0000000..6e53cdd --- /dev/null +++ b/pycparser/ply/__init__.py @@ -0,0 +1,5 @@ +# PLY package +# Author: David Beazley (dave@dabeaz.com) + +__version__ = '3.9' +__all__ = ['lex','yacc'] diff --git a/pycparser/ply/cpp.py b/pycparser/ply/cpp.py new file mode 100644 index 0000000..86273ea --- /dev/null +++ b/pycparser/ply/cpp.py @@ -0,0 +1,905 @@ +# ----------------------------------------------------------------------------- +# cpp.py +# +# Author: David Beazley (http://www.dabeaz.com) +# Copyright (C) 2017 +# All rights reserved +# +# This module implements an ANSI-C style lexical preprocessor for PLY. +# ----------------------------------------------------------------------------- +import sys + +# Some Python 3 compatibility shims +if sys.version_info.major < 3: + STRING_TYPES = (str, unicode) +else: + STRING_TYPES = str + xrange = range + +# ----------------------------------------------------------------------------- +# Default preprocessor lexer definitions. These tokens are enough to get +# a basic preprocessor working. Other modules may import these if they want +# ----------------------------------------------------------------------------- + +tokens = ( + 'CPP_ID','CPP_INTEGER', 'CPP_FLOAT', 'CPP_STRING', 'CPP_CHAR', 'CPP_WS', 'CPP_COMMENT1', 'CPP_COMMENT2', 'CPP_POUND','CPP_DPOUND' +) + +literals = "+-*/%|&~^<>=!?()[]{}.,;:\\\'\"" + +# Whitespace +def t_CPP_WS(t): + r'\s+' + t.lexer.lineno += t.value.count("\n") + return t + +t_CPP_POUND = r'\#' +t_CPP_DPOUND = r'\#\#' + +# Identifier +t_CPP_ID = r'[A-Za-z_][\w_]*' + +# Integer literal +def CPP_INTEGER(t): + r'(((((0x)|(0X))[0-9a-fA-F]+)|(\d+))([uU][lL]|[lL][uU]|[uU]|[lL])?)' + return t + +t_CPP_INTEGER = CPP_INTEGER + +# Floating literal +t_CPP_FLOAT = r'((\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+))([lL]|[fF])?' + +# String literal +def t_CPP_STRING(t): + r'\"([^\\\n]|(\\(.|\n)))*?\"' + t.lexer.lineno += t.value.count("\n") + return t + +# Character constant 'c' or L'c' +def t_CPP_CHAR(t): + r'(L)?\'([^\\\n]|(\\(.|\n)))*?\'' + t.lexer.lineno += t.value.count("\n") + return t + +# Comment +def t_CPP_COMMENT1(t): + r'(/\*(.|\n)*?\*/)' + ncr = t.value.count("\n") + t.lexer.lineno += ncr + # replace with one space or a number of '\n' + t.type = 'CPP_WS'; t.value = '\n' * ncr if ncr else ' ' + return t + +# Line comment +def t_CPP_COMMENT2(t): + r'(//.*?(\n|$))' + # replace with '/n' + t.type = 'CPP_WS'; t.value = '\n' + return t + +def t_error(t): + t.type = t.value[0] + t.value = t.value[0] + t.lexer.skip(1) + return t + +import re +import copy +import time +import os.path + +# ----------------------------------------------------------------------------- +# trigraph() +# +# Given an input string, this function replaces all trigraph sequences. +# The following mapping is used: +# +# ??= # +# ??/ \ +# ??' ^ +# ??( [ +# ??) ] +# ??! | +# ??< { +# ??> } +# ??- ~ +# ----------------------------------------------------------------------------- + +_trigraph_pat = re.compile(r'''\?\?[=/\'\(\)\!<>\-]''') +_trigraph_rep = { + '=':'#', + '/':'\\', + "'":'^', + '(':'[', + ')':']', + '!':'|', + '<':'{', + '>':'}', + '-':'~' +} + +def trigraph(input): + return _trigraph_pat.sub(lambda g: _trigraph_rep[g.group()[-1]],input) + +# ------------------------------------------------------------------ +# Macro object +# +# This object holds information about preprocessor macros +# +# .name - Macro name (string) +# .value - Macro value (a list of tokens) +# .arglist - List of argument names +# .variadic - Boolean indicating whether or not variadic macro +# .vararg - Name of the variadic parameter +# +# When a macro is created, the macro replacement token sequence is +# pre-scanned and used to create patch lists that are later used +# during macro expansion +# ------------------------------------------------------------------ + +class Macro(object): + def __init__(self,name,value,arglist=None,variadic=False): + self.name = name + self.value = value + self.arglist = arglist + self.variadic = variadic + if variadic: + self.vararg = arglist[-1] + self.source = None + +# ------------------------------------------------------------------ +# Preprocessor object +# +# Object representing a preprocessor. Contains macro definitions, +# include directories, and other information +# ------------------------------------------------------------------ + +class Preprocessor(object): + def __init__(self,lexer=None): + if lexer is None: + lexer = lex.lexer + self.lexer = lexer + self.macros = { } + self.path = [] + self.temp_path = [] + + # Probe the lexer for selected tokens + self.lexprobe() + + tm = time.localtime() + self.define("__DATE__ \"%s\"" % time.strftime("%b %d %Y",tm)) + self.define("__TIME__ \"%s\"" % time.strftime("%H:%M:%S",tm)) + self.parser = None + + # ----------------------------------------------------------------------------- + # tokenize() + # + # Utility function. Given a string of text, tokenize into a list of tokens + # ----------------------------------------------------------------------------- + + def tokenize(self,text): + tokens = [] + self.lexer.input(text) + while True: + tok = self.lexer.token() + if not tok: break + tokens.append(tok) + return tokens + + # --------------------------------------------------------------------- + # error() + # + # Report a preprocessor error/warning of some kind + # ---------------------------------------------------------------------- + + def error(self,file,line,msg): + print("%s:%d %s" % (file,line,msg)) + + # ---------------------------------------------------------------------- + # lexprobe() + # + # This method probes the preprocessor lexer object to discover + # the token types of symbols that are important to the preprocessor. + # If this works right, the preprocessor will simply "work" + # with any suitable lexer regardless of how tokens have been named. + # ---------------------------------------------------------------------- + + def lexprobe(self): + + # Determine the token type for identifiers + self.lexer.input("identifier") + tok = self.lexer.token() + if not tok or tok.value != "identifier": + print("Couldn't determine identifier type") + else: + self.t_ID = tok.type + + # Determine the token type for integers + self.lexer.input("12345") + tok = self.lexer.token() + if not tok or int(tok.value) != 12345: + print("Couldn't determine integer type") + else: + self.t_INTEGER = tok.type + self.t_INTEGER_TYPE = type(tok.value) + + # Determine the token type for strings enclosed in double quotes + self.lexer.input("\"filename\"") + tok = self.lexer.token() + if not tok or tok.value != "\"filename\"": + print("Couldn't determine string type") + else: + self.t_STRING = tok.type + + # Determine the token type for whitespace--if any + self.lexer.input(" ") + tok = self.lexer.token() + if not tok or tok.value != " ": + self.t_SPACE = None + else: + self.t_SPACE = tok.type + + # Determine the token type for newlines + self.lexer.input("\n") + tok = self.lexer.token() + if not tok or tok.value != "\n": + self.t_NEWLINE = None + print("Couldn't determine token for newlines") + else: + self.t_NEWLINE = tok.type + + self.t_WS = (self.t_SPACE, self.t_NEWLINE) + + # Check for other characters used by the preprocessor + chars = [ '<','>','#','##','\\','(',')',',','.'] + for c in chars: + self.lexer.input(c) + tok = self.lexer.token() + if not tok or tok.value != c: + print("Unable to lex '%s' required for preprocessor" % c) + + # ---------------------------------------------------------------------- + # add_path() + # + # Adds a search path to the preprocessor. + # ---------------------------------------------------------------------- + + def add_path(self,path): + self.path.append(path) + + # ---------------------------------------------------------------------- + # group_lines() + # + # Given an input string, this function splits it into lines. Trailing whitespace + # is removed. Any line ending with \ is grouped with the next line. This + # function forms the lowest level of the preprocessor---grouping into text into + # a line-by-line format. + # ---------------------------------------------------------------------- + + def group_lines(self,input): + lex = self.lexer.clone() + lines = [x.rstrip() for x in input.splitlines()] + for i in xrange(len(lines)): + j = i+1 + while lines[i].endswith('\\') and (j < len(lines)): + lines[i] = lines[i][:-1]+lines[j] + lines[j] = "" + j += 1 + + input = "\n".join(lines) + lex.input(input) + lex.lineno = 1 + + current_line = [] + while True: + tok = lex.token() + if not tok: + break + current_line.append(tok) + if tok.type in self.t_WS and '\n' in tok.value: + yield current_line + current_line = [] + + if current_line: + yield current_line + + # ---------------------------------------------------------------------- + # tokenstrip() + # + # Remove leading/trailing whitespace tokens from a token list + # ---------------------------------------------------------------------- + + def tokenstrip(self,tokens): + i = 0 + while i < len(tokens) and tokens[i].type in self.t_WS: + i += 1 + del tokens[:i] + i = len(tokens)-1 + while i >= 0 and tokens[i].type in self.t_WS: + i -= 1 + del tokens[i+1:] + return tokens + + + # ---------------------------------------------------------------------- + # collect_args() + # + # Collects comma separated arguments from a list of tokens. The arguments + # must be enclosed in parenthesis. Returns a tuple (tokencount,args,positions) + # where tokencount is the number of tokens consumed, args is a list of arguments, + # and positions is a list of integers containing the starting index of each + # argument. Each argument is represented by a list of tokens. + # + # When collecting arguments, leading and trailing whitespace is removed + # from each argument. + # + # This function properly handles nested parenthesis and commas---these do not + # define new arguments. + # ---------------------------------------------------------------------- + + def collect_args(self,tokenlist): + args = [] + positions = [] + current_arg = [] + nesting = 1 + tokenlen = len(tokenlist) + + # Search for the opening '('. + i = 0 + while (i < tokenlen) and (tokenlist[i].type in self.t_WS): + i += 1 + + if (i < tokenlen) and (tokenlist[i].value == '('): + positions.append(i+1) + else: + self.error(self.source,tokenlist[0].lineno,"Missing '(' in macro arguments") + return 0, [], [] + + i += 1 + + while i < tokenlen: + t = tokenlist[i] + if t.value == '(': + current_arg.append(t) + nesting += 1 + elif t.value == ')': + nesting -= 1 + if nesting == 0: + if current_arg: + args.append(self.tokenstrip(current_arg)) + positions.append(i) + return i+1,args,positions + current_arg.append(t) + elif t.value == ',' and nesting == 1: + args.append(self.tokenstrip(current_arg)) + positions.append(i+1) + current_arg = [] + else: + current_arg.append(t) + i += 1 + + # Missing end argument + self.error(self.source,tokenlist[-1].lineno,"Missing ')' in macro arguments") + return 0, [],[] + + # ---------------------------------------------------------------------- + # macro_prescan() + # + # Examine the macro value (token sequence) and identify patch points + # This is used to speed up macro expansion later on---we'll know + # right away where to apply patches to the value to form the expansion + # ---------------------------------------------------------------------- + + def macro_prescan(self,macro): + macro.patch = [] # Standard macro arguments + macro.str_patch = [] # String conversion expansion + macro.var_comma_patch = [] # Variadic macro comma patch + i = 0 + while i < len(macro.value): + if macro.value[i].type == self.t_ID and macro.value[i].value in macro.arglist: + argnum = macro.arglist.index(macro.value[i].value) + # Conversion of argument to a string + if i > 0 and macro.value[i-1].value == '#': + macro.value[i] = copy.copy(macro.value[i]) + macro.value[i].type = self.t_STRING + del macro.value[i-1] + macro.str_patch.append((argnum,i-1)) + continue + # Concatenation + elif (i > 0 and macro.value[i-1].value == '##'): + macro.patch.append(('c',argnum,i-1)) + del macro.value[i-1] + continue + elif ((i+1) < len(macro.value) and macro.value[i+1].value == '##'): + macro.patch.append(('c',argnum,i)) + i += 1 + continue + # Standard expansion + else: + macro.patch.append(('e',argnum,i)) + elif macro.value[i].value == '##': + if macro.variadic and (i > 0) and (macro.value[i-1].value == ',') and \ + ((i+1) < len(macro.value)) and (macro.value[i+1].type == self.t_ID) and \ + (macro.value[i+1].value == macro.vararg): + macro.var_comma_patch.append(i-1) + i += 1 + macro.patch.sort(key=lambda x: x[2],reverse=True) + + # ---------------------------------------------------------------------- + # macro_expand_args() + # + # Given a Macro and list of arguments (each a token list), this method + # returns an expanded version of a macro. The return value is a token sequence + # representing the replacement macro tokens + # ---------------------------------------------------------------------- + + def macro_expand_args(self,macro,args): + # Make a copy of the macro token sequence + rep = [copy.copy(_x) for _x in macro.value] + + # Make string expansion patches. These do not alter the length of the replacement sequence + + str_expansion = {} + for argnum, i in macro.str_patch: + if argnum not in str_expansion: + str_expansion[argnum] = ('"%s"' % "".join([x.value for x in args[argnum]])).replace("\\","\\\\") + rep[i] = copy.copy(rep[i]) + rep[i].value = str_expansion[argnum] + + # Make the variadic macro comma patch. If the variadic macro argument is empty, we get rid + comma_patch = False + if macro.variadic and not args[-1]: + for i in macro.var_comma_patch: + rep[i] = None + comma_patch = True + + # Make all other patches. The order of these matters. It is assumed that the patch list + # has been sorted in reverse order of patch location since replacements will cause the + # size of the replacement sequence to expand from the patch point. + + expanded = { } + for ptype, argnum, i in macro.patch: + # Concatenation. Argument is left unexpanded + if ptype == 'c': + rep[i:i+1] = args[argnum] + # Normal expansion. Argument is macro expanded first + elif ptype == 'e': + if argnum not in expanded: + expanded[argnum] = self.expand_macros(args[argnum]) + rep[i:i+1] = expanded[argnum] + + # Get rid of removed comma if necessary + if comma_patch: + rep = [_i for _i in rep if _i] + + return rep + + + # ---------------------------------------------------------------------- + # expand_macros() + # + # Given a list of tokens, this function performs macro expansion. + # The expanded argument is a dictionary that contains macros already + # expanded. This is used to prevent infinite recursion. + # ---------------------------------------------------------------------- + + def expand_macros(self,tokens,expanded=None): + if expanded is None: + expanded = {} + i = 0 + while i < len(tokens): + t = tokens[i] + if t.type == self.t_ID: + if t.value in self.macros and t.value not in expanded: + # Yes, we found a macro match + expanded[t.value] = True + + m = self.macros[t.value] + if not m.arglist: + # A simple macro + ex = self.expand_macros([copy.copy(_x) for _x in m.value],expanded) + for e in ex: + e.lineno = t.lineno + tokens[i:i+1] = ex + i += len(ex) + else: + # A macro with arguments + j = i + 1 + while j < len(tokens) and tokens[j].type in self.t_WS: + j += 1 + if tokens[j].value == '(': + tokcount,args,positions = self.collect_args(tokens[j:]) + if not m.variadic and len(args) != len(m.arglist): + self.error(self.source,t.lineno,"Macro %s requires %d arguments" % (t.value,len(m.arglist))) + i = j + tokcount + elif m.variadic and len(args) < len(m.arglist)-1: + if len(m.arglist) > 2: + self.error(self.source,t.lineno,"Macro %s must have at least %d arguments" % (t.value, len(m.arglist)-1)) + else: + self.error(self.source,t.lineno,"Macro %s must have at least %d argument" % (t.value, len(m.arglist)-1)) + i = j + tokcount + else: + if m.variadic: + if len(args) == len(m.arglist)-1: + args.append([]) + else: + args[len(m.arglist)-1] = tokens[j+positions[len(m.arglist)-1]:j+tokcount-1] + del args[len(m.arglist):] + + # Get macro replacement text + rep = self.macro_expand_args(m,args) + rep = self.expand_macros(rep,expanded) + for r in rep: + r.lineno = t.lineno + tokens[i:j+tokcount] = rep + i += len(rep) + del expanded[t.value] + continue + elif t.value == '__LINE__': + t.type = self.t_INTEGER + t.value = self.t_INTEGER_TYPE(t.lineno) + + i += 1 + return tokens + + # ---------------------------------------------------------------------- + # evalexpr() + # + # Evaluate an expression token sequence for the purposes of evaluating + # integral expressions. + # ---------------------------------------------------------------------- + + def evalexpr(self,tokens): + # tokens = tokenize(line) + # Search for defined macros + i = 0 + while i < len(tokens): + if tokens[i].type == self.t_ID and tokens[i].value == 'defined': + j = i + 1 + needparen = False + result = "0L" + while j < len(tokens): + if tokens[j].type in self.t_WS: + j += 1 + continue + elif tokens[j].type == self.t_ID: + if tokens[j].value in self.macros: + result = "1L" + else: + result = "0L" + if not needparen: break + elif tokens[j].value == '(': + needparen = True + elif tokens[j].value == ')': + break + else: + self.error(self.source,tokens[i].lineno,"Malformed defined()") + j += 1 + tokens[i].type = self.t_INTEGER + tokens[i].value = self.t_INTEGER_TYPE(result) + del tokens[i+1:j+1] + i += 1 + tokens = self.expand_macros(tokens) + for i,t in enumerate(tokens): + if t.type == self.t_ID: + tokens[i] = copy.copy(t) + tokens[i].type = self.t_INTEGER + tokens[i].value = self.t_INTEGER_TYPE("0L") + elif t.type == self.t_INTEGER: + tokens[i] = copy.copy(t) + # Strip off any trailing suffixes + tokens[i].value = str(tokens[i].value) + while tokens[i].value[-1] not in "0123456789abcdefABCDEF": + tokens[i].value = tokens[i].value[:-1] + + expr = "".join([str(x.value) for x in tokens]) + expr = expr.replace("&&"," and ") + expr = expr.replace("||"," or ") + expr = expr.replace("!"," not ") + try: + result = eval(expr) + except Exception: + self.error(self.source,tokens[0].lineno,"Couldn't evaluate expression") + result = 0 + return result + + # ---------------------------------------------------------------------- + # parsegen() + # + # Parse an input string/ + # ---------------------------------------------------------------------- + def parsegen(self,input,source=None): + + # Replace trigraph sequences + t = trigraph(input) + lines = self.group_lines(t) + + if not source: + source = "" + + self.define("__FILE__ \"%s\"" % source) + + self.source = source + chunk = [] + enable = True + iftrigger = False + ifstack = [] + + for x in lines: + for i,tok in enumerate(x): + if tok.type not in self.t_WS: break + if tok.value == '#': + # Preprocessor directive + + # insert necessary whitespace instead of eaten tokens + for tok in x: + if tok.type in self.t_WS and '\n' in tok.value: + chunk.append(tok) + + dirtokens = self.tokenstrip(x[i+1:]) + if dirtokens: + name = dirtokens[0].value + args = self.tokenstrip(dirtokens[1:]) + else: + name = "" + args = [] + + if name == 'define': + if enable: + for tok in self.expand_macros(chunk): + yield tok + chunk = [] + self.define(args) + elif name == 'include': + if enable: + for tok in self.expand_macros(chunk): + yield tok + chunk = [] + oldfile = self.macros['__FILE__'] + for tok in self.include(args): + yield tok + self.macros['__FILE__'] = oldfile + self.source = source + elif name == 'undef': + if enable: + for tok in self.expand_macros(chunk): + yield tok + chunk = [] + self.undef(args) + elif name == 'ifdef': + ifstack.append((enable,iftrigger)) + if enable: + if not args[0].value in self.macros: + enable = False + iftrigger = False + else: + iftrigger = True + elif name == 'ifndef': + ifstack.append((enable,iftrigger)) + if enable: + if args[0].value in self.macros: + enable = False + iftrigger = False + else: + iftrigger = True + elif name == 'if': + ifstack.append((enable,iftrigger)) + if enable: + result = self.evalexpr(args) + if not result: + enable = False + iftrigger = False + else: + iftrigger = True + elif name == 'elif': + if ifstack: + if ifstack[-1][0]: # We only pay attention if outer "if" allows this + if enable: # If already true, we flip enable False + enable = False + elif not iftrigger: # If False, but not triggered yet, we'll check expression + result = self.evalexpr(args) + if result: + enable = True + iftrigger = True + else: + self.error(self.source,dirtokens[0].lineno,"Misplaced #elif") + + elif name == 'else': + if ifstack: + if ifstack[-1][0]: + if enable: + enable = False + elif not iftrigger: + enable = True + iftrigger = True + else: + self.error(self.source,dirtokens[0].lineno,"Misplaced #else") + + elif name == 'endif': + if ifstack: + enable,iftrigger = ifstack.pop() + else: + self.error(self.source,dirtokens[0].lineno,"Misplaced #endif") + else: + # Unknown preprocessor directive + pass + + else: + # Normal text + if enable: + chunk.extend(x) + + for tok in self.expand_macros(chunk): + yield tok + chunk = [] + + # ---------------------------------------------------------------------- + # include() + # + # Implementation of file-inclusion + # ---------------------------------------------------------------------- + + def include(self,tokens): + # Try to extract the filename and then process an include file + if not tokens: + return + if tokens: + if tokens[0].value != '<' and tokens[0].type != self.t_STRING: + tokens = self.expand_macros(tokens) + + if tokens[0].value == '<': + # Include <...> + i = 1 + while i < len(tokens): + if tokens[i].value == '>': + break + i += 1 + else: + print("Malformed #include <...>") + return + filename = "".join([x.value for x in tokens[1:i]]) + path = self.path + [""] + self.temp_path + elif tokens[0].type == self.t_STRING: + filename = tokens[0].value[1:-1] + path = self.temp_path + [""] + self.path + else: + print("Malformed #include statement") + return + for p in path: + iname = os.path.join(p,filename) + try: + data = open(iname,"r").read() + dname = os.path.dirname(iname) + if dname: + self.temp_path.insert(0,dname) + for tok in self.parsegen(data,filename): + yield tok + if dname: + del self.temp_path[0] + break + except IOError: + pass + else: + print("Couldn't find '%s'" % filename) + + # ---------------------------------------------------------------------- + # define() + # + # Define a new macro + # ---------------------------------------------------------------------- + + def define(self,tokens): + if isinstance(tokens,STRING_TYPES): + tokens = self.tokenize(tokens) + + linetok = tokens + try: + name = linetok[0] + if len(linetok) > 1: + mtype = linetok[1] + else: + mtype = None + if not mtype: + m = Macro(name.value,[]) + self.macros[name.value] = m + elif mtype.type in self.t_WS: + # A normal macro + m = Macro(name.value,self.tokenstrip(linetok[2:])) + self.macros[name.value] = m + elif mtype.value == '(': + # A macro with arguments + tokcount, args, positions = self.collect_args(linetok[1:]) + variadic = False + for a in args: + if variadic: + print("No more arguments may follow a variadic argument") + break + astr = "".join([str(_i.value) for _i in a]) + if astr == "...": + variadic = True + a[0].type = self.t_ID + a[0].value = '__VA_ARGS__' + variadic = True + del a[1:] + continue + elif astr[-3:] == "..." and a[0].type == self.t_ID: + variadic = True + del a[1:] + # If, for some reason, "." is part of the identifier, strip off the name for the purposes + # of macro expansion + if a[0].value[-3:] == '...': + a[0].value = a[0].value[:-3] + continue + if len(a) > 1 or a[0].type != self.t_ID: + print("Invalid macro argument") + break + else: + mvalue = self.tokenstrip(linetok[1+tokcount:]) + i = 0 + while i < len(mvalue): + if i+1 < len(mvalue): + if mvalue[i].type in self.t_WS and mvalue[i+1].value == '##': + del mvalue[i] + continue + elif mvalue[i].value == '##' and mvalue[i+1].type in self.t_WS: + del mvalue[i+1] + i += 1 + m = Macro(name.value,mvalue,[x[0].value for x in args],variadic) + self.macro_prescan(m) + self.macros[name.value] = m + else: + print("Bad macro definition") + except LookupError: + print("Bad macro definition") + + # ---------------------------------------------------------------------- + # undef() + # + # Undefine a macro + # ---------------------------------------------------------------------- + + def undef(self,tokens): + id = tokens[0].value + try: + del self.macros[id] + except LookupError: + pass + + # ---------------------------------------------------------------------- + # parse() + # + # Parse input text. + # ---------------------------------------------------------------------- + def parse(self,input,source=None,ignore={}): + self.ignore = ignore + self.parser = self.parsegen(input,source) + + # ---------------------------------------------------------------------- + # token() + # + # Method to return individual tokens + # ---------------------------------------------------------------------- + def token(self): + try: + while True: + tok = next(self.parser) + if tok.type not in self.ignore: return tok + except StopIteration: + self.parser = None + return None + +if __name__ == '__main__': + import ply.lex as lex + lexer = lex.lex() + + # Run a preprocessor + import sys + f = open(sys.argv[1]) + input = f.read() + + p = Preprocessor(lexer) + p.parse(input,sys.argv[1]) + while True: + tok = p.token() + if not tok: break + print(p.source, tok) diff --git a/pycparser/ply/ctokens.py b/pycparser/ply/ctokens.py new file mode 100644 index 0000000..f6f6952 --- /dev/null +++ b/pycparser/ply/ctokens.py @@ -0,0 +1,133 @@ +# ---------------------------------------------------------------------- +# ctokens.py +# +# Token specifications for symbols in ANSI C and C++. This file is +# meant to be used as a library in other tokenizers. +# ---------------------------------------------------------------------- + +# Reserved words + +tokens = [ + # Literals (identifier, integer constant, float constant, string constant, char const) + 'ID', 'TYPEID', 'INTEGER', 'FLOAT', 'STRING', 'CHARACTER', + + # Operators (+,-,*,/,%,|,&,~,^,<<,>>, ||, &&, !, <, <=, >, >=, ==, !=) + 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MODULO', + 'OR', 'AND', 'NOT', 'XOR', 'LSHIFT', 'RSHIFT', + 'LOR', 'LAND', 'LNOT', + 'LT', 'LE', 'GT', 'GE', 'EQ', 'NE', + + # Assignment (=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=) + 'EQUALS', 'TIMESEQUAL', 'DIVEQUAL', 'MODEQUAL', 'PLUSEQUAL', 'MINUSEQUAL', + 'LSHIFTEQUAL','RSHIFTEQUAL', 'ANDEQUAL', 'XOREQUAL', 'OREQUAL', + + # Increment/decrement (++,--) + 'INCREMENT', 'DECREMENT', + + # Structure dereference (->) + 'ARROW', + + # Ternary operator (?) + 'TERNARY', + + # Delimeters ( ) [ ] { } , . ; : + 'LPAREN', 'RPAREN', + 'LBRACKET', 'RBRACKET', + 'LBRACE', 'RBRACE', + 'COMMA', 'PERIOD', 'SEMI', 'COLON', + + # Ellipsis (...) + 'ELLIPSIS', +] + +# Operators +t_PLUS = r'\+' +t_MINUS = r'-' +t_TIMES = r'\*' +t_DIVIDE = r'/' +t_MODULO = r'%' +t_OR = r'\|' +t_AND = r'&' +t_NOT = r'~' +t_XOR = r'\^' +t_LSHIFT = r'<<' +t_RSHIFT = r'>>' +t_LOR = r'\|\|' +t_LAND = r'&&' +t_LNOT = r'!' +t_LT = r'<' +t_GT = r'>' +t_LE = r'<=' +t_GE = r'>=' +t_EQ = r'==' +t_NE = r'!=' + +# Assignment operators + +t_EQUALS = r'=' +t_TIMESEQUAL = r'\*=' +t_DIVEQUAL = r'/=' +t_MODEQUAL = r'%=' +t_PLUSEQUAL = r'\+=' +t_MINUSEQUAL = r'-=' +t_LSHIFTEQUAL = r'<<=' +t_RSHIFTEQUAL = r'>>=' +t_ANDEQUAL = r'&=' +t_OREQUAL = r'\|=' +t_XOREQUAL = r'\^=' + +# Increment/decrement +t_INCREMENT = r'\+\+' +t_DECREMENT = r'--' + +# -> +t_ARROW = r'->' + +# ? +t_TERNARY = r'\?' + +# Delimeters +t_LPAREN = r'\(' +t_RPAREN = r'\)' +t_LBRACKET = r'\[' +t_RBRACKET = r'\]' +t_LBRACE = r'\{' +t_RBRACE = r'\}' +t_COMMA = r',' +t_PERIOD = r'\.' +t_SEMI = r';' +t_COLON = r':' +t_ELLIPSIS = r'\.\.\.' + +# Identifiers +t_ID = r'[A-Za-z_][A-Za-z0-9_]*' + +# Integer literal +t_INTEGER = r'\d+([uU]|[lL]|[uU][lL]|[lL][uU])?' + +# Floating literal +t_FLOAT = r'((\d+)(\.\d+)(e(\+|-)?(\d+))? | (\d+)e(\+|-)?(\d+))([lL]|[fF])?' + +# String literal +t_STRING = r'\"([^\\\n]|(\\.))*?\"' + +# Character constant 'c' or L'c' +t_CHARACTER = r'(L)?\'([^\\\n]|(\\.))*?\'' + +# Comment (C-Style) +def t_COMMENT(t): + r'/\*(.|\n)*?\*/' + t.lexer.lineno += t.value.count('\n') + return t + +# Comment (C++-Style) +def t_CPPCOMMENT(t): + r'//.*\n' + t.lexer.lineno += 1 + return t + + + + + + diff --git a/pycparser/ply/lex.py b/pycparser/ply/lex.py new file mode 100644 index 0000000..4bdd76c --- /dev/null +++ b/pycparser/ply/lex.py @@ -0,0 +1,1099 @@ +# ----------------------------------------------------------------------------- +# ply: lex.py +# +# Copyright (C) 2001-2017 +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the 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. +# ----------------------------------------------------------------------------- + +__version__ = '3.10' +__tabversion__ = '3.10' + +import re +import sys +import types +import copy +import os +import inspect + +# This tuple contains known string types +try: + # Python 2.6 + StringTypes = (types.StringType, types.UnicodeType) +except AttributeError: + # Python 3.0 + 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 __str__(self): + return 'LexToken(%s,%r,%d,%d)' % (self.type, self.value, self.lineno, self.lexpos) + + def __repr__(self): + return str(self) + + +# 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 + + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self, name): + return self + + def __call__(self, *args, **kwargs): + return self + + +# ----------------------------------------------------------------------------- +# === 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 + self.lexoptimize = False # Optimized mode + + 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 + + # ------------------------------------------------------------ + # writetab() - Write lexer information to a table file + # ------------------------------------------------------------ + def writetab(self, lextab, outputdir=''): + if isinstance(lextab, types.ModuleType): + raise IOError("Won't overwrite existing lextab module") + basetabmodule = lextab.split('.')[-1] + filename = os.path.join(outputdir, basetabmodule) + '.py' + with open(filename, 'w') as tf: + tf.write('# %s.py. This file automatically created by PLY (version %s). Don\'t edit!\n' % (basetabmodule, __version__)) + tf.write('_tabversion = %s\n' % repr(__tabversion__)) + tf.write('_lextokens = set(%s)\n' % repr(tuple(self.lextokens))) + tf.write('_lexreflags = %s\n' % repr(self.lexreflags)) + tf.write('_lexliterals = %s\n' % repr(self.lexliterals)) + tf.write('_lexstateinfo = %s\n' % repr(self.lexstateinfo)) + + # Rewrite the lexstatere table, replacing function objects with function names + tabre = {} + for statename, lre in self.lexstatere.items(): + titem = [] + for (pat, func), retext, renames in zip(lre, self.lexstateretext[statename], self.lexstaterenames[statename]): + titem.append((retext, _funcs_to_names(func, renames))) + tabre[statename] = titem + + tf.write('_lexstatere = %s\n' % repr(tabre)) + tf.write('_lexstateignore = %s\n' % repr(self.lexstateignore)) + + taberr = {} + for statename, ef in self.lexstateerrorf.items(): + taberr[statename] = ef.__name__ if ef else None + tf.write('_lexstateerrorf = %s\n' % repr(taberr)) + + tabeof = {} + for statename, ef in self.lexstateeoff.items(): + tabeof[statename] = ef.__name__ if ef else None + tf.write('_lexstateeoff = %s\n' % repr(tabeof)) + + # ------------------------------------------------------------ + # readtab() - Read lexer information from a tab file + # ------------------------------------------------------------ + def readtab(self, tabfile, fdict): + if isinstance(tabfile, types.ModuleType): + lextab = tabfile + else: + exec('import %s' % tabfile) + lextab = sys.modules[tabfile] + + if getattr(lextab, '_tabversion', '0.0') != __tabversion__: + raise ImportError('Inconsistent PLY version') + + self.lextokens = lextab._lextokens + self.lexreflags = lextab._lexreflags + self.lexliterals = lextab._lexliterals + self.lextokens_all = self.lextokens | set(self.lexliterals) + self.lexstateinfo = lextab._lexstateinfo + self.lexstateignore = lextab._lexstateignore + self.lexstatere = {} + self.lexstateretext = {} + for statename, lre in lextab._lexstatere.items(): + titem = [] + txtitem = [] + for pat, func_name in lre: + titem.append((re.compile(pat, lextab._lexreflags), _names_to_funcs(func_name, fdict))) + + self.lexstatere[statename] = titem + self.lexstateretext[statename] = txtitem + + self.lexstateerrorf = {} + for statename, ef in lextab._lexstateerrorf.items(): + self.lexstateerrorf[statename] = fdict[ef] + + self.lexstateeoff = {} + for statename, ef in lextab._lexstateeoff.items(): + self.lexstateeoff[statename] = fdict[ef] + + self.begin('INITIAL') + + # ------------------------------------------------------------ + # input() - Push a new string into the lexer + # ------------------------------------------------------------ + def input(self, s): + # Pull off the first character to see if s looks like a string + c = s[:1] + if not isinstance(c, StringTypes): + raise ValueError('Expected a string') + 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('Undefined state') + 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 + + # ------------------------------------------------------------ + # opttoken() - 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) + + # 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 + + # Verify type of the token. If not in the token map, raise an error + if not self.lexoptimize: + if newtok.type not in self.lextokens_all: + raise LexError("%s:%d: Rule '%s' returned an unknown token type '%s'" % ( + func.__code__.co_filename, func.__code__.co_firstlineno, + func.__name__, newtok.type), lexdata[lexpos:]) + + 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("Scanning error. Illegal character '%s'" % (lexdata[lexpos]), lexdata[lexpos:]) + lexpos = self.lexpos + if not newtok: + continue + return newtok + + self.lexpos = lexpos + raise LexError("Illegal character '%s' at index %d" % (lexdata[lexpos], 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 + + __next__ = next + +# ----------------------------------------------------------------------------- +# ==== 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) + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + return ldict + +# ----------------------------------------------------------------------------- +# _funcs_to_names() +# +# Given a list of regular expression functions, this converts it to a list +# suitable for output to a table file +# ----------------------------------------------------------------------------- +def _funcs_to_names(funclist, namelist): + result = [] + for f, name in zip(funclist, namelist): + if f and f[0]: + result.append((name, f[1])) + else: + result.append(f) + return result + +# ----------------------------------------------------------------------------- +# _names_to_funcs() +# +# Given a list of regular expression function names, this converts it back to +# functions. +# ----------------------------------------------------------------------------- +def _names_to_funcs(namelist, fdict): + result = [] + for n in namelist: + if n and n[0]: + result.append((fdict[n[0]], n[1])) + else: + result.append(n) + return result + +# ----------------------------------------------------------------------------- +# _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 = int(len(relist)/2) + if m == 0: + m = 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): + nonstate = 1 + 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("Bad token name '%s'", n) + self.error = True + if n in terminals: + self.log.warning("Token '%s' multiply defined", n) + 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('Invalid literal %s. Must be a single character', repr(c)) + 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 %s. Must be a tuple (statename,'exclusive|inclusive')", repr(s)) + self.error = True + continue + name, statetype = s + if not isinstance(name, StringTypes): + self.log.error('State name %s must be a string', repr(name)) + self.error = True + continue + if not (statetype == 'inclusive' or statetype == 'exclusive'): + self.log.error("State type for state %s must be 'inclusive' or 'exclusive'", name) + self.error = True + continue + if name in self.stateinfo: + self.log.error("State '%s' 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 '%s' 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 '%s' 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 '%s' has too many arguments", file, line, f.__name__) + self.error = True + continue + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' 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 '%s'", 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 '%s' 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 '%s' 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 '%s' 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 '%s' 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 '%s' matches empty string", name) + self.error = True + except re.error as e: + self.log.error("Invalid regular expression for rule '%s'. %s", name, e) + if '#' in r: + self.log.error("Make sure '#' in rule '%s' 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 '%s'", 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 '%s' has too many arguments", file, line, f.__name__) + self.error = True + + if nargs < reqargs: + self.log.error("%s:%d: Rule '%s' 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, optimize=False, lextab='lextab', + reflags=int(re.VERBOSE), nowarn=False, outputdir=None, debuglog=None, errorlog=None): + + if lextab is None: + lextab = 'lextab' + + global lexer + + ldict = None + stateinfo = {'INITIAL': 'inclusive'} + lexobj = Lexer() + lexobj.lexoptimize = optimize + 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) + + # Determine if the module is package of a package or not. + # If so, fix the tabmodule setting so that tables load correctly + pkg = ldict.get('__package__') + if pkg and isinstance(lextab, str): + if '.' not in lextab: + lextab = pkg + '.' + lextab + + # Collect parser information from the dictionary + linfo = LexerReflect(ldict, log=errorlog, reflags=reflags) + linfo.get_all() + if not optimize: + if linfo.validate_all(): + raise SyntaxError("Can't build lexer") + + if optimize and lextab: + try: + lexobj.readtab(lextab, ldict) + token = lexobj.token + input = lexobj.input + lexer = lexobj + return lexobj + + except ImportError: + pass + + # 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]: + line = f.__code__.co_firstlineno + file = f.__code__.co_filename + 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 '%s'", s) + if s not in linfo.ignore and lexobj.lexignore: + errorlog.warning("No ignore rule is defined for exclusive state '%s'", 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 + + # If in optimize mode, we write the lextab + if lextab and optimize: + if outputdir is None: + # If no output directory is set, the location of the output files + # is determined according to the following rules: + # - If lextab specifies a package, files go into that package directory + # - Otherwise, files go in the same directory as the specifying module + if isinstance(lextab, types.ModuleType): + srcfile = lextab.__file__ + else: + if '.' not in lextab: + srcfile = ldict['__file__'] + else: + parts = lextab.split('.') + pkgname = '.'.join(parts[:-1]) + exec('import %s' % pkgname) + srcfile = getattr(sys.modules[pkgname], '__file__', '') + outputdir = os.path.dirname(srcfile) + try: + lexobj.writetab(lextab, outputdir) + except IOError as e: + errorlog.warning("Couldn't write lextab module %r. %s" % (lextab, e)) + + 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] + f = open(filename) + data = f.read() + f.close() + 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('(%s,%r,%d,%d)\n' % (tok.type, tok.value, tok.lineno, tok.lexpos)) + +# ----------------------------------------------------------------------------- +# @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 + +# Alternative spelling of the TOKEN decorator +Token = TOKEN diff --git a/pycparser/ply/yacc.py b/pycparser/ply/yacc.py new file mode 100644 index 0000000..20b4f28 --- /dev/null +++ b/pycparser/ply/yacc.py @@ -0,0 +1,3494 @@ +# ----------------------------------------------------------------------------- +# ply: yacc.py +# +# Copyright (C) 2001-2017 +# David M. Beazley (Dabeaz LLC) +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of the 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. +# ----------------------------------------------------------------------------- +# +# This implements an LR parser that is constructed from grammar rules defined +# as Python functions. The grammer is specified by supplying the BNF inside +# Python documentation strings. The inspiration for this technique was borrowed +# from John Aycock's Spark parsing system. PLY might be viewed as cross between +# Spark and the GNU bison utility. +# +# The current implementation is only somewhat object-oriented. The +# LR parser itself is defined in terms of an object (which allows multiple +# parsers to co-exist). However, most of the variables used during table +# construction are defined in terms of global variables. Users shouldn't +# notice unless they are trying to define multiple parsers at the same +# time using threads (in which case they should have their head examined). +# +# This implementation supports both SLR and LALR(1) parsing. LALR(1) +# support was originally implemented by Elias Ioup (ezioup@alumni.uchicago.edu), +# using the algorithm found in Aho, Sethi, and Ullman "Compilers: Principles, +# Techniques, and Tools" (The Dragon Book). LALR(1) has since been replaced +# by the more efficient DeRemer and Pennello algorithm. +# +# :::::::: WARNING ::::::: +# +# Construction of LR parsing tables is fairly complicated and expensive. +# To make this module run fast, a *LOT* of work has been put into +# optimization---often at the expensive of readability and what might +# consider to be good Python "coding style." Modify the code at your +# own risk! +# ---------------------------------------------------------------------------- + +import re +import types +import sys +import os.path +import inspect +import base64 +import warnings + +__version__ = '3.10' +__tabversion__ = '3.10' + +#----------------------------------------------------------------------------- +# === User configurable parameters === +# +# Change these to modify the default behavior of yacc (if you wish) +#----------------------------------------------------------------------------- + +yaccdebug = True # Debugging mode. If set, yacc generates a + # a 'parser.out' file in the current directory + +debug_file = 'parser.out' # Default name of the debugging file +tab_module = 'parsetab' # Default name of the table module +default_lr = 'LALR' # Default LR table generation method + +error_count = 3 # Number of symbols that must be shifted to leave recovery mode + +yaccdevel = False # Set to True if developing yacc. This turns off optimized + # implementations of certain functions. + +resultlimit = 40 # Size limit of results when running in debug mode. + +pickle_protocol = 0 # Protocol to use when writing pickle files + +# String type-checking compatibility +if sys.version_info[0] < 3: + string_types = basestring +else: + string_types = str + +MAXINT = sys.maxsize + +# This object is a stand-in for a logging object created by the +# logging module. PLY will use this by default to create things +# such as the parser.out file. If a user wants more detailed +# information, they can create their own logging object and pass +# it into PLY. + +class PlyLogger(object): + def __init__(self, f): + self.f = f + + def debug(self, msg, *args, **kwargs): + self.f.write((msg % args) + '\n') + + info = debug + + 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') + + critical = debug + +# Null logger is used when no output is generated. Does nothing. +class NullLogger(object): + def __getattribute__(self, name): + return self + + def __call__(self, *args, **kwargs): + return self + +# Exception raised for yacc-related errors +class YaccError(Exception): + pass + +# Format the result message that the parser produces when running in debug mode. +def format_result(r): + repr_str = repr(r) + if '\n' in repr_str: + repr_str = repr(repr_str) + if len(repr_str) > resultlimit: + repr_str = repr_str[:resultlimit] + ' ...' + result = '<%s @ 0x%x> (%s)' % (type(r).__name__, id(r), repr_str) + return result + +# Format stack entries when the parser is running in debug mode +def format_stack_entry(r): + repr_str = repr(r) + if '\n' in repr_str: + repr_str = repr(repr_str) + if len(repr_str) < 16: + return repr_str + else: + return '<%s @ 0x%x>' % (type(r).__name__, id(r)) + +# Panic mode error recovery support. This feature is being reworked--much of the +# code here is to offer a deprecation/backwards compatible transition + +_errok = None +_token = None +_restart = None +_warnmsg = '''PLY: Don't use global functions errok(), token(), and restart() in p_error(). +Instead, invoke the methods on the associated parser instance: + + def p_error(p): + ... + # Use parser.errok(), parser.token(), parser.restart() + ... + + parser = yacc.yacc() +''' + +def errok(): + warnings.warn(_warnmsg) + return _errok() + +def restart(): + warnings.warn(_warnmsg) + return _restart() + +def token(): + warnings.warn(_warnmsg) + return _token() + +# Utility function to call the p_error() function with some deprecation hacks +def call_errorfunc(errorfunc, token, parser): + global _errok, _token, _restart + _errok = parser.errok + _token = parser.token + _restart = parser.restart + r = errorfunc(token) + try: + del _errok, _token, _restart + except NameError: + pass + return r + +#----------------------------------------------------------------------------- +# === LR Parsing Engine === +# +# The following classes are used for the LR parser itself. These are not +# used during table construction and are independent of the actual LR +# table generation algorithm +#----------------------------------------------------------------------------- + +# This class is used to hold non-terminal grammar symbols during parsing. +# It normally has the following attributes set: +# .type = Grammar symbol type +# .value = Symbol value +# .lineno = Starting line number +# .endlineno = Ending line number (optional, set automatically) +# .lexpos = Starting lex position +# .endlexpos = Ending lex position (optional, set automatically) + +class YaccSymbol: + def __str__(self): + return self.type + + def __repr__(self): + return str(self) + +# This class is a wrapper around the objects actually passed to each +# grammar rule. Index lookup and assignment actually assign the +# .value attribute of the underlying YaccSymbol object. +# The lineno() method returns the line number of a given +# item (or 0 if not defined). The linespan() method returns +# a tuple of (startline,endline) representing the range of lines +# for a symbol. The lexspan() method returns a tuple (lexpos,endlexpos) +# representing the range of positional information for a symbol. + +class YaccProduction: + def __init__(self, s, stack=None): + self.slice = s + self.stack = stack + self.lexer = None + self.parser = None + + def __getitem__(self, n): + if isinstance(n, slice): + return [s.value for s in self.slice[n]] + elif n >= 0: + return self.slice[n].value + else: + return self.stack[n].value + + def __setitem__(self, n, v): + self.slice[n].value = v + + def __getslice__(self, i, j): + return [s.value for s in self.slice[i:j]] + + def __len__(self): + return len(self.slice) + + def lineno(self, n): + return getattr(self.slice[n], 'lineno', 0) + + def set_lineno(self, n, lineno): + self.slice[n].lineno = lineno + + def linespan(self, n): + startline = getattr(self.slice[n], 'lineno', 0) + endline = getattr(self.slice[n], 'endlineno', startline) + return startline, endline + + def lexpos(self, n): + return getattr(self.slice[n], 'lexpos', 0) + + def lexspan(self, n): + startpos = getattr(self.slice[n], 'lexpos', 0) + endpos = getattr(self.slice[n], 'endlexpos', startpos) + return startpos, endpos + + def error(self): + raise SyntaxError + +# ----------------------------------------------------------------------------- +# == LRParser == +# +# The LR Parsing engine. +# ----------------------------------------------------------------------------- + +class LRParser: + def __init__(self, lrtab, errorf): + self.productions = lrtab.lr_productions + self.action = lrtab.lr_action + self.goto = lrtab.lr_goto + self.errorfunc = errorf + self.set_defaulted_states() + self.errorok = True + + def errok(self): + self.errorok = True + + def restart(self): + del self.statestack[:] + del self.symstack[:] + sym = YaccSymbol() + sym.type = '$end' + self.symstack.append(sym) + self.statestack.append(0) + + # Defaulted state support. + # This method identifies parser states where there is only one possible reduction action. + # For such states, the parser can make a choose to make a rule reduction without consuming + # the next look-ahead token. This delayed invocation of the tokenizer can be useful in + # certain kinds of advanced parsing situations where the lexer and parser interact with + # each other or change states (i.e., manipulation of scope, lexer states, etc.). + # + # See: https://www.gnu.org/software/bison/manual/html_node/Default-Reductions.html#Default-Reductions + def set_defaulted_states(self): + self.defaulted_states = {} + for state, actions in self.action.items(): + rules = list(actions.values()) + if len(rules) == 1 and rules[0] < 0: + self.defaulted_states[state] = rules[0] + + def disable_defaulted_states(self): + self.defaulted_states = {} + + def parse(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + if debug or yaccdevel: + if isinstance(debug, int): + debug = PlyLogger(sys.stderr) + return self.parsedebug(input, lexer, debug, tracking, tokenfunc) + elif tracking: + return self.parseopt(input, lexer, debug, tracking, tokenfunc) + else: + return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc) + + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parsedebug(). + # + # This is the debugging enabled version of parse(). All changes made to the + # parsing engine should be made here. Optimized versions of this function + # are automatically created by the ply/ygen.py script. This script cuts out + # sections enclosed in markers such as this: + # + # #--! DEBUG + # statements + # #--! DEBUG + # + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parsedebug(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parsedebug-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + #--! DEBUG + debug.info('PLY: PARSE DEBUG START') + #--! DEBUG + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + #--! DEBUG + debug.debug('') + debug.debug('State : %s', state) + #--! DEBUG + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + #--! DEBUG + debug.debug('Defaulted state %s: Reduce using %d', state, -t) + #--! DEBUG + + #--! DEBUG + debug.debug('Stack : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + #--! DEBUG + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + #--! DEBUG + debug.debug('Action : Shift and goto state %s', t) + #--! DEBUG + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + #--! DEBUG + if plen: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, + '['+','.join([format_stack_entry(_v.value) for _v in symstack[-plen:]])+']', + goto[statestack[-1-plen]][pname]) + else: + debug.info('Action : Reduce rule [%s] with %s and goto state %d', p.str, [], + goto[statestack[-1]][pname]) + + #--! DEBUG + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + #--! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1, 'endlineno', t1.lineno) + sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) + #--! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + #--! DEBUG + debug.info('Result : %s', format_result(pslice[0])) + #--! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + #--! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + #--! TRACKING + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + #--! DEBUG + debug.info('Result : %s', format_result(pslice[0])) + #--! DEBUG + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + #--! DEBUG + debug.info('Done : Returning %s', format_result(result)) + debug.info('PLY: PARSE DEBUG END') + #--! DEBUG + return result + + if t is None: + + #--! DEBUG + debug.error('Error : %s', + ('%s . %s' % (' '.join([xx.type for xx in symstack][1:]), str(lookahead))).lstrip()) + #--! DEBUG + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + #--! TRACKING + if tracking: + sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) + sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) + #--! TRACKING + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + #--! TRACKING + if tracking: + lookahead.lineno = sym.lineno + lookahead.lexpos = sym.lexpos + #--! TRACKING + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parsedebug-end + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt(). + # + # Optimized version of parse() method. DO NOT EDIT THIS CODE DIRECTLY! + # This code is automatically generated by the ply/ygen.py script. Make + # changes to the parsedebug() method instead. + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parseopt(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parseopt-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + #--! TRACKING + if tracking: + t1 = targ[1] + sym.lineno = t1.lineno + sym.lexpos = t1.lexpos + t1 = targ[-1] + sym.endlineno = getattr(t1, 'endlineno', t1.lineno) + sym.endlexpos = getattr(t1, 'endlexpos', t1.lexpos) + #--! TRACKING + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + #--! TRACKING + if tracking: + sym.lineno = lexer.lineno + sym.lexpos = lexer.lexpos + #--! TRACKING + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + return result + + if t is None: + + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + #--! TRACKING + if tracking: + sym.endlineno = getattr(lookahead, 'lineno', sym.lineno) + sym.endlexpos = getattr(lookahead, 'lexpos', sym.lexpos) + #--! TRACKING + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + #--! TRACKING + if tracking: + lookahead.lineno = sym.lineno + lookahead.lexpos = sym.lexpos + #--! TRACKING + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parseopt-end + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # parseopt_notrack(). + # + # Optimized version of parseopt() with line number tracking removed. + # DO NOT EDIT THIS CODE DIRECTLY. This code is automatically generated + # by the ply/ygen.py script. Make changes to the parsedebug() method instead. + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + def parseopt_notrack(self, input=None, lexer=None, debug=False, tracking=False, tokenfunc=None): + #--! parseopt-notrack-start + lookahead = None # Current lookahead symbol + lookaheadstack = [] # Stack of lookahead symbols + actions = self.action # Local reference to action table (to avoid lookup on self.) + goto = self.goto # Local reference to goto table (to avoid lookup on self.) + prod = self.productions # Local reference to production list (to avoid lookup on self.) + defaulted_states = self.defaulted_states # Local reference to defaulted states + pslice = YaccProduction(None) # Production object passed to grammar rules + errorcount = 0 # Used during error recovery + + + # If no lexer was given, we will try to use the lex module + if not lexer: + from . import lex + lexer = lex.lexer + + # Set up the lexer and parser objects on pslice + pslice.lexer = lexer + pslice.parser = self + + # If input was supplied, pass to lexer + if input is not None: + lexer.input(input) + + if tokenfunc is None: + # Tokenize function + get_token = lexer.token + else: + get_token = tokenfunc + + # Set the parser() token method (sometimes used in error recovery) + self.token = get_token + + # Set up the state and symbol stacks + + statestack = [] # Stack of parsing states + self.statestack = statestack + symstack = [] # Stack of grammar symbols + self.symstack = symstack + + pslice.stack = symstack # Put in the production + errtoken = None # Err token + + # The start state is assumed to be (0,$end) + + statestack.append(0) + sym = YaccSymbol() + sym.type = '$end' + symstack.append(sym) + state = 0 + while True: + # Get the next symbol on the input. If a lookahead symbol + # is already set, we just use that. Otherwise, we'll pull + # the next token off of the lookaheadstack or from the lexer + + + if state not in defaulted_states: + if not lookahead: + if not lookaheadstack: + lookahead = get_token() # Get the next token + else: + lookahead = lookaheadstack.pop() + if not lookahead: + lookahead = YaccSymbol() + lookahead.type = '$end' + + # Check the action table + ltype = lookahead.type + t = actions[state].get(ltype) + else: + t = defaulted_states[state] + + + if t is not None: + if t > 0: + # shift a symbol on the stack + statestack.append(t) + state = t + + + symstack.append(lookahead) + lookahead = None + + # Decrease error count on successful shift + if errorcount: + errorcount -= 1 + continue + + if t < 0: + # reduce a symbol on the stack, emit a production + p = prod[-t] + pname = p.name + plen = p.len + + # Get production function + sym = YaccSymbol() + sym.type = pname # Production name + sym.value = None + + + if plen: + targ = symstack[-plen-1:] + targ[0] = sym + + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # below as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + del symstack[-plen:] + self.state = state + p.callable(pslice) + del statestack[-plen:] + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + symstack.extend(targ[1:-1]) # Put the production slice back on the stack + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + else: + + + targ = [sym] + + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # The code enclosed in this section is duplicated + # above as a performance optimization. Make sure + # changes get made in both locations. + + pslice.slice = targ + + try: + # Call the grammar rule with our special slice object + self.state = state + p.callable(pslice) + symstack.append(sym) + state = goto[statestack[-1]][pname] + statestack.append(state) + except SyntaxError: + # If an error was set. Enter error recovery state + lookaheadstack.append(lookahead) # Save the current lookahead token + statestack.pop() # Pop back one state (before the reduce) + state = statestack[-1] + sym.type = 'error' + sym.value = 'error' + lookahead = sym + errorcount = error_count + self.errorok = False + + continue + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + if t == 0: + n = symstack[-1] + result = getattr(n, 'value', None) + return result + + if t is None: + + + # We have some kind of parsing error here. To handle + # this, we are going to push the current token onto + # the tokenstack and replace it with an 'error' token. + # If there are any synchronization rules, they may + # catch it. + # + # In addition to pushing the error token, we call call + # the user defined p_error() function if this is the + # first syntax error. This function is only called if + # errorcount == 0. + if errorcount == 0 or self.errorok: + errorcount = error_count + self.errorok = False + errtoken = lookahead + if errtoken.type == '$end': + errtoken = None # End of file! + if self.errorfunc: + if errtoken and not hasattr(errtoken, 'lexer'): + errtoken.lexer = lexer + self.state = state + tok = call_errorfunc(self.errorfunc, errtoken, self) + if self.errorok: + # User must have done some kind of panic + # mode recovery on their own. The + # returned token is the next lookahead + lookahead = tok + errtoken = None + continue + else: + if errtoken: + if hasattr(errtoken, 'lineno'): + lineno = lookahead.lineno + else: + lineno = 0 + if lineno: + sys.stderr.write('yacc: Syntax error at line %d, token=%s\n' % (lineno, errtoken.type)) + else: + sys.stderr.write('yacc: Syntax error, token=%s' % errtoken.type) + else: + sys.stderr.write('yacc: Parse error in input. EOF\n') + return + + else: + errorcount = error_count + + # case 1: the statestack only has 1 entry on it. If we're in this state, the + # entire parse has been rolled back and we're completely hosed. The token is + # discarded and we just keep going. + + if len(statestack) <= 1 and lookahead.type != '$end': + lookahead = None + errtoken = None + state = 0 + # Nuke the pushback stack + del lookaheadstack[:] + continue + + # case 2: the statestack has a couple of entries on it, but we're + # at the end of the file. nuke the top entry and generate an error token + + # Start nuking entries on the stack + if lookahead.type == '$end': + # Whoa. We're really hosed here. Bail out + return + + if lookahead.type != 'error': + sym = symstack[-1] + if sym.type == 'error': + # Hmmm. Error is on top of stack, we'll just nuke input + # symbol and continue + lookahead = None + continue + + # Create the error symbol for the first time and make it the new lookahead symbol + t = YaccSymbol() + t.type = 'error' + + if hasattr(lookahead, 'lineno'): + t.lineno = t.endlineno = lookahead.lineno + if hasattr(lookahead, 'lexpos'): + t.lexpos = t.endlexpos = lookahead.lexpos + t.value = lookahead + lookaheadstack.append(lookahead) + lookahead = t + else: + sym = symstack.pop() + statestack.pop() + state = statestack[-1] + + continue + + # Call an error function here + raise RuntimeError('yacc: internal parser error!!!\n') + + #--! parseopt-notrack-end + +# ----------------------------------------------------------------------------- +# === Grammar Representation === +# +# The following functions, classes, and variables are used to represent and +# manipulate the rules that make up a grammar. +# ----------------------------------------------------------------------------- + +# regex matching identifiers +_is_identifier = re.compile(r'^[a-zA-Z0-9_-]+$') + +# ----------------------------------------------------------------------------- +# class Production: +# +# This class stores the raw information about a single production or grammar rule. +# A grammar rule refers to a specification such as this: +# +# expr : expr PLUS term +# +# Here are the basic attributes defined on all productions +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','PLUS','term'] +# prec - Production precedence level +# number - Production number. +# func - Function that executes on reduce +# file - File where production function is defined +# lineno - Line number where production function is defined +# +# The following attributes are defined or optional. +# +# len - Length of the production (number of symbols on right hand side) +# usyms - Set of unique symbols found in the production +# ----------------------------------------------------------------------------- + +class Production(object): + reduced = 0 + def __init__(self, number, name, prod, precedence=('right', 0), func=None, file='', line=0): + self.name = name + self.prod = tuple(prod) + self.number = number + self.func = func + self.callable = None + self.file = file + self.line = line + self.prec = precedence + + # Internal settings used during table construction + + self.len = len(self.prod) # Length of the production + + # Create a list of unique production symbols used in the production + self.usyms = [] + for s in self.prod: + if s not in self.usyms: + self.usyms.append(s) + + # List of all LR items for the production + self.lr_items = [] + self.lr_next = None + + # Create a string representation + if self.prod: + self.str = '%s -> %s' % (self.name, ' '.join(self.prod)) + else: + self.str = '%s -> <empty>' % self.name + + def __str__(self): + return self.str + + def __repr__(self): + return 'Production(' + str(self) + ')' + + def __len__(self): + return len(self.prod) + + def __nonzero__(self): + return 1 + + def __getitem__(self, index): + return self.prod[index] + + # Return the nth lr_item from the production (or None if at the end) + def lr_item(self, n): + if n > len(self.prod): + return None + p = LRItem(self, n) + # Precompute the list of productions immediately following. + try: + p.lr_after = Prodnames[p.prod[n+1]] + except (IndexError, KeyError): + p.lr_after = [] + try: + p.lr_before = p.prod[n-1] + except IndexError: + p.lr_before = None + return p + + # Bind the production function name to a callable + def bind(self, pdict): + if self.func: + self.callable = pdict[self.func] + +# This class serves as a minimal standin for Production objects when +# reading table data from files. It only contains information +# actually used by the LR parsing engine, plus some additional +# debugging information. +class MiniProduction(object): + def __init__(self, str, name, len, func, file, line): + self.name = name + self.len = len + self.func = func + self.callable = None + self.file = file + self.line = line + self.str = str + + def __str__(self): + return self.str + + def __repr__(self): + return 'MiniProduction(%s)' % self.str + + # Bind the production function name to a callable + def bind(self, pdict): + if self.func: + self.callable = pdict[self.func] + + +# ----------------------------------------------------------------------------- +# class LRItem +# +# This class represents a specific stage of parsing a production rule. For +# example: +# +# expr : expr . PLUS term +# +# In the above, the "." represents the current location of the parse. Here +# basic attributes: +# +# name - Name of the production. For example 'expr' +# prod - A list of symbols on the right side ['expr','.', 'PLUS','term'] +# number - Production number. +# +# lr_next Next LR item. Example, if we are ' expr -> expr . PLUS term' +# then lr_next refers to 'expr -> expr PLUS . term' +# lr_index - LR item index (location of the ".") in the prod list. +# lookaheads - LALR lookahead symbols for this item +# len - Length of the production (number of symbols on right hand side) +# lr_after - List of all productions that immediately follow +# lr_before - Grammar symbol immediately before +# ----------------------------------------------------------------------------- + +class LRItem(object): + def __init__(self, p, n): + self.name = p.name + self.prod = list(p.prod) + self.number = p.number + self.lr_index = n + self.lookaheads = {} + self.prod.insert(n, '.') + self.prod = tuple(self.prod) + self.len = len(self.prod) + self.usyms = p.usyms + + def __str__(self): + if self.prod: + s = '%s -> %s' % (self.name, ' '.join(self.prod)) + else: + s = '%s -> <empty>' % self.name + return s + + def __repr__(self): + return 'LRItem(' + str(self) + ')' + +# ----------------------------------------------------------------------------- +# rightmost_terminal() +# +# Return the rightmost terminal from a list of symbols. Used in add_production() +# ----------------------------------------------------------------------------- +def rightmost_terminal(symbols, terminals): + i = len(symbols) - 1 + while i >= 0: + if symbols[i] in terminals: + return symbols[i] + i -= 1 + return None + +# ----------------------------------------------------------------------------- +# === GRAMMAR CLASS === +# +# The following class represents the contents of the specified grammar along +# with various computed properties such as first sets, follow sets, LR items, etc. +# This data is used for critical parts of the table generation process later. +# ----------------------------------------------------------------------------- + +class GrammarError(YaccError): + pass + +class Grammar(object): + def __init__(self, terminals): + self.Productions = [None] # A list of all of the productions. The first + # entry is always reserved for the purpose of + # building an augmented grammar + + self.Prodnames = {} # A dictionary mapping the names of nonterminals to a list of all + # productions of that nonterminal. + + self.Prodmap = {} # A dictionary that is only used to detect duplicate + # productions. + + self.Terminals = {} # A dictionary mapping the names of terminal symbols to a + # list of the rules where they are used. + + for term in terminals: + self.Terminals[term] = [] + + self.Terminals['error'] = [] + + self.Nonterminals = {} # A dictionary mapping names of nonterminals to a list + # of rule numbers where they are used. + + self.First = {} # A dictionary of precomputed FIRST(x) symbols + + self.Follow = {} # A dictionary of precomputed FOLLOW(x) symbols + + self.Precedence = {} # Precedence rules for each terminal. Contains tuples of the + # form ('right',level) or ('nonassoc', level) or ('left',level) + + self.UsedPrecedence = set() # Precedence rules that were actually used by the grammer. + # This is only used to provide error checking and to generate + # a warning about unused precedence rules. + + self.Start = None # Starting symbol for the grammar + + + def __len__(self): + return len(self.Productions) + + def __getitem__(self, index): + return self.Productions[index] + + # ----------------------------------------------------------------------------- + # set_precedence() + # + # Sets the precedence for a given terminal. assoc is the associativity such as + # 'left','right', or 'nonassoc'. level is a numeric level. + # + # ----------------------------------------------------------------------------- + + def set_precedence(self, term, assoc, level): + assert self.Productions == [None], 'Must call set_precedence() before add_production()' + if term in self.Precedence: + raise GrammarError('Precedence already specified for terminal %r' % term) + if assoc not in ['left', 'right', 'nonassoc']: + raise GrammarError("Associativity must be one of 'left','right', or 'nonassoc'") + self.Precedence[term] = (assoc, level) + + # ----------------------------------------------------------------------------- + # add_production() + # + # Given an action function, this function assembles a production rule and + # computes its precedence level. + # + # The production rule is supplied as a list of symbols. For example, + # a rule such as 'expr : expr PLUS term' has a production name of 'expr' and + # symbols ['expr','PLUS','term']. + # + # Precedence is determined by the precedence of the right-most non-terminal + # or the precedence of a terminal specified by %prec. + # + # A variety of error checks are performed to make sure production symbols + # are valid and that %prec is used correctly. + # ----------------------------------------------------------------------------- + + def add_production(self, prodname, syms, func=None, file='', line=0): + + if prodname in self.Terminals: + raise GrammarError('%s:%d: Illegal rule name %r. Already defined as a token' % (file, line, prodname)) + if prodname == 'error': + raise GrammarError('%s:%d: Illegal rule name %r. error is a reserved word' % (file, line, prodname)) + if not _is_identifier.match(prodname): + raise GrammarError('%s:%d: Illegal rule name %r' % (file, line, prodname)) + + # Look for literal tokens + for n, s in enumerate(syms): + if s[0] in "'\"": + try: + c = eval(s) + if (len(c) > 1): + raise GrammarError('%s:%d: Literal token %s in rule %r may only be a single character' % + (file, line, s, prodname)) + if c not in self.Terminals: + self.Terminals[c] = [] + syms[n] = c + continue + except SyntaxError: + pass + if not _is_identifier.match(s) and s != '%prec': + raise GrammarError('%s:%d: Illegal name %r in rule %r' % (file, line, s, prodname)) + + # Determine the precedence level + if '%prec' in syms: + if syms[-1] == '%prec': + raise GrammarError('%s:%d: Syntax error. Nothing follows %%prec' % (file, line)) + if syms[-2] != '%prec': + raise GrammarError('%s:%d: Syntax error. %%prec can only appear at the end of a grammar rule' % + (file, line)) + precname = syms[-1] + prodprec = self.Precedence.get(precname) + if not prodprec: + raise GrammarError('%s:%d: Nothing known about the precedence of %r' % (file, line, precname)) + else: + self.UsedPrecedence.add(precname) + del syms[-2:] # Drop %prec from the rule + else: + # If no %prec, precedence is determined by the rightmost terminal symbol + precname = rightmost_terminal(syms, self.Terminals) + prodprec = self.Precedence.get(precname, ('right', 0)) + + # See if the rule is already in the rulemap + map = '%s -> %s' % (prodname, syms) + if map in self.Prodmap: + m = self.Prodmap[map] + raise GrammarError('%s:%d: Duplicate rule %s. ' % (file, line, m) + + 'Previous definition at %s:%d' % (m.file, m.line)) + + # From this point on, everything is valid. Create a new Production instance + pnumber = len(self.Productions) + if prodname not in self.Nonterminals: + self.Nonterminals[prodname] = [] + + # Add the production number to Terminals and Nonterminals + for t in syms: + if t in self.Terminals: + self.Terminals[t].append(pnumber) + else: + if t not in self.Nonterminals: + self.Nonterminals[t] = [] + self.Nonterminals[t].append(pnumber) + + # Create a production and add it to the list of productions + p = Production(pnumber, prodname, syms, prodprec, func, file, line) + self.Productions.append(p) + self.Prodmap[map] = p + + # Add to the global productions list + try: + self.Prodnames[prodname].append(p) + except KeyError: + self.Prodnames[prodname] = [p] + + # ----------------------------------------------------------------------------- + # set_start() + # + # Sets the starting symbol and creates the augmented grammar. Production + # rule 0 is S' -> start where start is the start symbol. + # ----------------------------------------------------------------------------- + + def set_start(self, start=None): + if not start: + start = self.Productions[1].name + if start not in self.Nonterminals: + raise GrammarError('start symbol %s undefined' % start) + self.Productions[0] = Production(0, "S'", [start]) + self.Nonterminals[start].append(0) + self.Start = start + + # ----------------------------------------------------------------------------- + # find_unreachable() + # + # Find all of the nonterminal symbols that can't be reached from the starting + # symbol. Returns a list of nonterminals that can't be reached. + # ----------------------------------------------------------------------------- + + def find_unreachable(self): + + # Mark all symbols that are reachable from a symbol s + def mark_reachable_from(s): + if s in reachable: + return + reachable.add(s) + for p in self.Prodnames.get(s, []): + for r in p.prod: + mark_reachable_from(r) + + reachable = set() + mark_reachable_from(self.Productions[0].prod[0]) + return [s for s in self.Nonterminals if s not in reachable] + + # ----------------------------------------------------------------------------- + # infinite_cycles() + # + # This function looks at the various parsing rules and tries to detect + # infinite recursion cycles (grammar rules where there is no possible way + # to derive a string of only terminals). + # ----------------------------------------------------------------------------- + + def infinite_cycles(self): + terminates = {} + + # Terminals: + for t in self.Terminals: + terminates[t] = True + + terminates['$end'] = True + + # Nonterminals: + + # Initialize to false: + for n in self.Nonterminals: + terminates[n] = False + + # Then propagate termination until no change: + while True: + some_change = False + for (n, pl) in self.Prodnames.items(): + # Nonterminal n terminates iff any of its productions terminates. + for p in pl: + # Production p terminates iff all of its rhs symbols terminate. + for s in p.prod: + if not terminates[s]: + # The symbol s does not terminate, + # so production p does not terminate. + p_terminates = False + break + else: + # didn't break from the loop, + # so every symbol s terminates + # so production p terminates. + p_terminates = True + + if p_terminates: + # symbol n terminates! + if not terminates[n]: + terminates[n] = True + some_change = True + # Don't need to consider any more productions for this n. + break + + if not some_change: + break + + infinite = [] + for (s, term) in terminates.items(): + if not term: + if s not in self.Prodnames and s not in self.Terminals and s != 'error': + # s is used-but-not-defined, and we've already warned of that, + # so it would be overkill to say that it's also non-terminating. + pass + else: + infinite.append(s) + + return infinite + + # ----------------------------------------------------------------------------- + # undefined_symbols() + # + # Find all symbols that were used the grammar, but not defined as tokens or + # grammar rules. Returns a list of tuples (sym, prod) where sym in the symbol + # and prod is the production where the symbol was used. + # ----------------------------------------------------------------------------- + def undefined_symbols(self): + result = [] + for p in self.Productions: + if not p: + continue + + for s in p.prod: + if s not in self.Prodnames and s not in self.Terminals and s != 'error': + result.append((s, p)) + return result + + # ----------------------------------------------------------------------------- + # unused_terminals() + # + # Find all terminals that were defined, but not used by the grammar. Returns + # a list of all symbols. + # ----------------------------------------------------------------------------- + def unused_terminals(self): + unused_tok = [] + for s, v in self.Terminals.items(): + if s != 'error' and not v: + unused_tok.append(s) + + return unused_tok + + # ------------------------------------------------------------------------------ + # unused_rules() + # + # Find all grammar rules that were defined, but not used (maybe not reachable) + # Returns a list of productions. + # ------------------------------------------------------------------------------ + + def unused_rules(self): + unused_prod = [] + for s, v in self.Nonterminals.items(): + if not v: + p = self.Prodnames[s][0] + unused_prod.append(p) + return unused_prod + + # ----------------------------------------------------------------------------- + # unused_precedence() + # + # Returns a list of tuples (term,precedence) corresponding to precedence + # rules that were never used by the grammar. term is the name of the terminal + # on which precedence was applied and precedence is a string such as 'left' or + # 'right' corresponding to the type of precedence. + # ----------------------------------------------------------------------------- + + def unused_precedence(self): + unused = [] + for termname in self.Precedence: + if not (termname in self.Terminals or termname in self.UsedPrecedence): + unused.append((termname, self.Precedence[termname][0])) + + return unused + + # ------------------------------------------------------------------------- + # _first() + # + # Compute the value of FIRST1(beta) where beta is a tuple of symbols. + # + # During execution of compute_first1, the result may be incomplete. + # Afterward (e.g., when called from compute_follow()), it will be complete. + # ------------------------------------------------------------------------- + def _first(self, beta): + + # We are computing First(x1,x2,x3,...,xn) + result = [] + for x in beta: + x_produces_empty = False + + # Add all the non-<empty> symbols of First[x] to the result. + for f in self.First[x]: + if f == '<empty>': + x_produces_empty = True + else: + if f not in result: + result.append(f) + + if x_produces_empty: + # We have to consider the next x in beta, + # i.e. stay in the loop. + pass + else: + # We don't have to consider any further symbols in beta. + break + else: + # There was no 'break' from the loop, + # so x_produces_empty was true for all x in beta, + # so beta produces empty as well. + result.append('<empty>') + + return result + + # ------------------------------------------------------------------------- + # compute_first() + # + # Compute the value of FIRST1(X) for all symbols + # ------------------------------------------------------------------------- + def compute_first(self): + if self.First: + return self.First + + # Terminals: + for t in self.Terminals: + self.First[t] = [t] + + self.First['$end'] = ['$end'] + + # Nonterminals: + + # Initialize to the empty set: + for n in self.Nonterminals: + self.First[n] = [] + + # Then propagate symbols until no change: + while True: + some_change = False + for n in self.Nonterminals: + for p in self.Prodnames[n]: + for f in self._first(p.prod): + if f not in self.First[n]: + self.First[n].append(f) + some_change = True + if not some_change: + break + + return self.First + + # --------------------------------------------------------------------- + # compute_follow() + # + # Computes all of the follow sets for every non-terminal symbol. The + # follow set is the set of all symbols that might follow a given + # non-terminal. See the Dragon book, 2nd Ed. p. 189. + # --------------------------------------------------------------------- + def compute_follow(self, start=None): + # If already computed, return the result + if self.Follow: + return self.Follow + + # If first sets not computed yet, do that first. + if not self.First: + self.compute_first() + + # Add '$end' to the follow list of the start symbol + for k in self.Nonterminals: + self.Follow[k] = [] + + if not start: + start = self.Productions[1].name + + self.Follow[start] = ['$end'] + + while True: + didadd = False + for p in self.Productions[1:]: + # Here is the production set + for i, B in enumerate(p.prod): + if B in self.Nonterminals: + # Okay. We got a non-terminal in a production + fst = self._first(p.prod[i+1:]) + hasempty = False + for f in fst: + if f != '<empty>' and f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = True + if f == '<empty>': + hasempty = True + if hasempty or i == (len(p.prod)-1): + # Add elements of follow(a) to follow(b) + for f in self.Follow[p.name]: + if f not in self.Follow[B]: + self.Follow[B].append(f) + didadd = True + if not didadd: + break + return self.Follow + + + # ----------------------------------------------------------------------------- + # build_lritems() + # + # This function walks the list of productions and builds a complete set of the + # LR items. The LR items are stored in two ways: First, they are uniquely + # numbered and placed in the list _lritems. Second, a linked list of LR items + # is built for each production. For example: + # + # E -> E PLUS E + # + # Creates the list + # + # [E -> . E PLUS E, E -> E . PLUS E, E -> E PLUS . E, E -> E PLUS E . ] + # ----------------------------------------------------------------------------- + + def build_lritems(self): + for p in self.Productions: + lastlri = p + i = 0 + lr_items = [] + while True: + if i > len(p): + lri = None + else: + lri = LRItem(p, i) + # Precompute the list of productions immediately following + try: + lri.lr_after = self.Prodnames[lri.prod[i+1]] + except (IndexError, KeyError): + lri.lr_after = [] + try: + lri.lr_before = lri.prod[i-1] + except IndexError: + lri.lr_before = None + + lastlri.lr_next = lri + if not lri: + break + lr_items.append(lri) + lastlri = lri + i += 1 + p.lr_items = lr_items + +# ----------------------------------------------------------------------------- +# == Class LRTable == +# +# This basic class represents a basic table of LR parsing information. +# Methods for generating the tables are not defined here. They are defined +# in the derived class LRGeneratedTable. +# ----------------------------------------------------------------------------- + +class VersionError(YaccError): + pass + +class LRTable(object): + def __init__(self): + self.lr_action = None + self.lr_goto = None + self.lr_productions = None + self.lr_method = None + + def read_table(self, module): + if isinstance(module, types.ModuleType): + parsetab = module + else: + exec('import %s' % module) + parsetab = sys.modules[module] + + if parsetab._tabversion != __tabversion__: + raise VersionError('yacc table file version is out of date') + + self.lr_action = parsetab._lr_action + self.lr_goto = parsetab._lr_goto + + self.lr_productions = [] + for p in parsetab._lr_productions: + self.lr_productions.append(MiniProduction(*p)) + + self.lr_method = parsetab._lr_method + return parsetab._lr_signature + + def read_pickle(self, filename): + try: + import cPickle as pickle + except ImportError: + import pickle + + if not os.path.exists(filename): + raise ImportError + + in_f = open(filename, 'rb') + + tabversion = pickle.load(in_f) + if tabversion != __tabversion__: + raise VersionError('yacc table file version is out of date') + self.lr_method = pickle.load(in_f) + signature = pickle.load(in_f) + self.lr_action = pickle.load(in_f) + self.lr_goto = pickle.load(in_f) + productions = pickle.load(in_f) + + self.lr_productions = [] + for p in productions: + self.lr_productions.append(MiniProduction(*p)) + + in_f.close() + return signature + + # Bind all production function names to callable objects in pdict + def bind_callables(self, pdict): + for p in self.lr_productions: + p.bind(pdict) + + +# ----------------------------------------------------------------------------- +# === LR Generator === +# +# The following classes and functions are used to generate LR parsing tables on +# a grammar. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# digraph() +# traverse() +# +# The following two functions are used to compute set valued functions +# of the form: +# +# F(x) = F'(x) U U{F(y) | x R y} +# +# This is used to compute the values of Read() sets as well as FOLLOW sets +# in LALR(1) generation. +# +# Inputs: X - An input set +# R - A relation +# FP - Set-valued function +# ------------------------------------------------------------------------------ + +def digraph(X, R, FP): + N = {} + for x in X: + N[x] = 0 + stack = [] + F = {} + for x in X: + if N[x] == 0: + traverse(x, N, stack, F, X, R, FP) + return F + +def traverse(x, N, stack, F, X, R, FP): + stack.append(x) + d = len(stack) + N[x] = d + F[x] = FP(x) # F(X) <- F'(x) + + rel = R(x) # Get y's related to x + for y in rel: + if N[y] == 0: + traverse(y, N, stack, F, X, R, FP) + N[x] = min(N[x], N[y]) + for a in F.get(y, []): + if a not in F[x]: + F[x].append(a) + if N[x] == d: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + while element != x: + N[stack[-1]] = MAXINT + F[stack[-1]] = F[x] + element = stack.pop() + +class LALRError(YaccError): + pass + +# ----------------------------------------------------------------------------- +# == LRGeneratedTable == +# +# This class implements the LR table generation algorithm. There are no +# public methods except for write() +# ----------------------------------------------------------------------------- + +class LRGeneratedTable(LRTable): + def __init__(self, grammar, method='LALR', log=None): + if method not in ['SLR', 'LALR']: + raise LALRError('Unsupported method %s' % method) + + self.grammar = grammar + self.lr_method = method + + # Set up the logger + if not log: + log = NullLogger() + self.log = log + + # Internal attributes + self.lr_action = {} # Action table + self.lr_goto = {} # Goto table + self.lr_productions = grammar.Productions # Copy of grammar Production array + self.lr_goto_cache = {} # Cache of computed gotos + self.lr0_cidhash = {} # Cache of closures + + self._add_count = 0 # Internal counter used to detect cycles + + # Diagonistic information filled in by the table generator + self.sr_conflict = 0 + self.rr_conflict = 0 + self.conflicts = [] # List of conflicts + + self.sr_conflicts = [] + self.rr_conflicts = [] + + # Build the tables + self.grammar.build_lritems() + self.grammar.compute_first() + self.grammar.compute_follow() + self.lr_parse_table() + + # Compute the LR(0) closure operation on I, where I is a set of LR(0) items. + + def lr0_closure(self, I): + self._add_count += 1 + + # Add everything in I to J + J = I[:] + didadd = True + while didadd: + didadd = False + for j in J: + for x in j.lr_after: + if getattr(x, 'lr0_added', 0) == self._add_count: + continue + # Add B --> .G to J + J.append(x.lr_next) + x.lr0_added = self._add_count + didadd = True + + return J + + # Compute the LR(0) goto function goto(I,X) where I is a set + # of LR(0) items and X is a grammar symbol. This function is written + # in a way that guarantees uniqueness of the generated goto sets + # (i.e. the same goto set will never be returned as two different Python + # objects). With uniqueness, we can later do fast set comparisons using + # id(obj) instead of element-wise comparison. + + def lr0_goto(self, I, x): + # First we look for a previously cached entry + g = self.lr_goto_cache.get((id(I), x)) + if g: + return g + + # Now we generate the goto set in a way that guarantees uniqueness + # of the result + + s = self.lr_goto_cache.get(x) + if not s: + s = {} + self.lr_goto_cache[x] = s + + gs = [] + for p in I: + n = p.lr_next + if n and n.lr_before == x: + s1 = s.get(id(n)) + if not s1: + s1 = {} + s[id(n)] = s1 + gs.append(n) + s = s1 + g = s.get('$end') + if not g: + if gs: + g = self.lr0_closure(gs) + s['$end'] = g + else: + s['$end'] = gs + self.lr_goto_cache[(id(I), x)] = g + return g + + # Compute the LR(0) sets of item function + def lr0_items(self): + C = [self.lr0_closure([self.grammar.Productions[0].lr_next])] + i = 0 + for I in C: + self.lr0_cidhash[id(I)] = i + i += 1 + + # Loop over the items in C and each grammar symbols + i = 0 + while i < len(C): + I = C[i] + i += 1 + + # Collect all of the symbols that could possibly be in the goto(I,X) sets + asyms = {} + for ii in I: + for s in ii.usyms: + asyms[s] = None + + for x in asyms: + g = self.lr0_goto(I, x) + if not g or id(g) in self.lr0_cidhash: + continue + self.lr0_cidhash[id(g)] = len(C) + C.append(g) + + return C + + # ----------------------------------------------------------------------------- + # ==== LALR(1) Parsing ==== + # + # LALR(1) parsing is almost exactly the same as SLR except that instead of + # relying upon Follow() sets when performing reductions, a more selective + # lookahead set that incorporates the state of the LR(0) machine is utilized. + # Thus, we mainly just have to focus on calculating the lookahead sets. + # + # The method used here is due to DeRemer and Pennelo (1982). + # + # DeRemer, F. L., and T. J. Pennelo: "Efficient Computation of LALR(1) + # Lookahead Sets", ACM Transactions on Programming Languages and Systems, + # Vol. 4, No. 4, Oct. 1982, pp. 615-649 + # + # Further details can also be found in: + # + # J. Tremblay and P. Sorenson, "The Theory and Practice of Compiler Writing", + # McGraw-Hill Book Company, (1985). + # + # ----------------------------------------------------------------------------- + + # ----------------------------------------------------------------------------- + # compute_nullable_nonterminals() + # + # Creates a dictionary containing all of the non-terminals that might produce + # an empty production. + # ----------------------------------------------------------------------------- + + def compute_nullable_nonterminals(self): + nullable = set() + num_nullable = 0 + while True: + for p in self.grammar.Productions[1:]: + if p.len == 0: + nullable.add(p.name) + continue + for t in p.prod: + if t not in nullable: + break + else: + nullable.add(p.name) + if len(nullable) == num_nullable: + break + num_nullable = len(nullable) + return nullable + + # ----------------------------------------------------------------------------- + # find_nonterminal_trans(C) + # + # Given a set of LR(0) items, this functions finds all of the non-terminal + # transitions. These are transitions in which a dot appears immediately before + # a non-terminal. Returns a list of tuples of the form (state,N) where state + # is the state number and N is the nonterminal symbol. + # + # The input C is the set of LR(0) items. + # ----------------------------------------------------------------------------- + + def find_nonterminal_transitions(self, C): + trans = [] + for stateno, state in enumerate(C): + for p in state: + if p.lr_index < p.len - 1: + t = (stateno, p.prod[p.lr_index+1]) + if t[1] in self.grammar.Nonterminals: + if t not in trans: + trans.append(t) + return trans + + # ----------------------------------------------------------------------------- + # dr_relation() + # + # Computes the DR(p,A) relationships for non-terminal transitions. The input + # is a tuple (state,N) where state is a number and N is a nonterminal symbol. + # + # Returns a list of terminals. + # ----------------------------------------------------------------------------- + + def dr_relation(self, C, trans, nullable): + dr_set = {} + state, N = trans + terms = [] + + g = self.lr0_goto(C[state], N) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index+1] + if a in self.grammar.Terminals: + if a not in terms: + terms.append(a) + + # This extra bit is to handle the start state + if state == 0 and N == self.grammar.Productions[0].prod[0]: + terms.append('$end') + + return terms + + # ----------------------------------------------------------------------------- + # reads_relation() + # + # Computes the READS() relation (p,A) READS (t,C). + # ----------------------------------------------------------------------------- + + def reads_relation(self, C, trans, empty): + # Look for empty transitions + rel = [] + state, N = trans + + g = self.lr0_goto(C[state], N) + j = self.lr0_cidhash.get(id(g), -1) + for p in g: + if p.lr_index < p.len - 1: + a = p.prod[p.lr_index + 1] + if a in empty: + rel.append((j, a)) + + return rel + + # ----------------------------------------------------------------------------- + # compute_lookback_includes() + # + # Determines the lookback and includes relations + # + # LOOKBACK: + # + # This relation is determined by running the LR(0) state machine forward. + # For example, starting with a production "N : . A B C", we run it forward + # to obtain "N : A B C ." We then build a relationship between this final + # state and the starting state. These relationships are stored in a dictionary + # lookdict. + # + # INCLUDES: + # + # Computes the INCLUDE() relation (p,A) INCLUDES (p',B). + # + # This relation is used to determine non-terminal transitions that occur + # inside of other non-terminal transition states. (p,A) INCLUDES (p', B) + # if the following holds: + # + # B -> LAT, where T -> epsilon and p' -L-> p + # + # L is essentially a prefix (which may be empty), T is a suffix that must be + # able to derive an empty string. State p' must lead to state p with the string L. + # + # ----------------------------------------------------------------------------- + + def compute_lookback_includes(self, C, trans, nullable): + lookdict = {} # Dictionary of lookback relations + includedict = {} # Dictionary of include relations + + # Make a dictionary of non-terminal transitions + dtrans = {} + for t in trans: + dtrans[t] = 1 + + # Loop over all transitions and compute lookbacks and includes + for state, N in trans: + lookb = [] + includes = [] + for p in C[state]: + if p.name != N: + continue + + # Okay, we have a name match. We now follow the production all the way + # through the state machine until we get the . on the right hand side + + lr_index = p.lr_index + j = state + while lr_index < p.len - 1: + lr_index = lr_index + 1 + t = p.prod[lr_index] + + # Check to see if this symbol and state are a non-terminal transition + if (j, t) in dtrans: + # Yes. Okay, there is some chance that this is an includes relation + # the only way to know for certain is whether the rest of the + # production derives empty + + li = lr_index + 1 + while li < p.len: + if p.prod[li] in self.grammar.Terminals: + break # No forget it + if p.prod[li] not in nullable: + break + li = li + 1 + else: + # Appears to be a relation between (j,t) and (state,N) + includes.append((j, t)) + + g = self.lr0_goto(C[j], t) # Go to next set + j = self.lr0_cidhash.get(id(g), -1) # Go to next state + + # When we get here, j is the final state, now we have to locate the production + for r in C[j]: + if r.name != p.name: + continue + if r.len != p.len: + continue + i = 0 + # This look is comparing a production ". A B C" with "A B C ." + while i < r.lr_index: + if r.prod[i] != p.prod[i+1]: + break + i = i + 1 + else: + lookb.append((j, r)) + for i in includes: + if i not in includedict: + includedict[i] = [] + includedict[i].append((state, N)) + lookdict[(state, N)] = lookb + + return lookdict, includedict + + # ----------------------------------------------------------------------------- + # compute_read_sets() + # + # Given a set of LR(0) items, this function computes the read sets. + # + # Inputs: C = Set of LR(0) items + # ntrans = Set of nonterminal transitions + # nullable = Set of empty transitions + # + # Returns a set containing the read sets + # ----------------------------------------------------------------------------- + + def compute_read_sets(self, C, ntrans, nullable): + FP = lambda x: self.dr_relation(C, x, nullable) + R = lambda x: self.reads_relation(C, x, nullable) + F = digraph(ntrans, R, FP) + return F + + # ----------------------------------------------------------------------------- + # compute_follow_sets() + # + # Given a set of LR(0) items, a set of non-terminal transitions, a readset, + # and an include set, this function computes the follow sets + # + # Follow(p,A) = Read(p,A) U U {Follow(p',B) | (p,A) INCLUDES (p',B)} + # + # Inputs: + # ntrans = Set of nonterminal transitions + # readsets = Readset (previously computed) + # inclsets = Include sets (previously computed) + # + # Returns a set containing the follow sets + # ----------------------------------------------------------------------------- + + def compute_follow_sets(self, ntrans, readsets, inclsets): + FP = lambda x: readsets[x] + R = lambda x: inclsets.get(x, []) + F = digraph(ntrans, R, FP) + return F + + # ----------------------------------------------------------------------------- + # add_lookaheads() + # + # Attaches the lookahead symbols to grammar rules. + # + # Inputs: lookbacks - Set of lookback relations + # followset - Computed follow set + # + # This function directly attaches the lookaheads to productions contained + # in the lookbacks set + # ----------------------------------------------------------------------------- + + def add_lookaheads(self, lookbacks, followset): + for trans, lb in lookbacks.items(): + # Loop over productions in lookback + for state, p in lb: + if state not in p.lookaheads: + p.lookaheads[state] = [] + f = followset.get(trans, []) + for a in f: + if a not in p.lookaheads[state]: + p.lookaheads[state].append(a) + + # ----------------------------------------------------------------------------- + # add_lalr_lookaheads() + # + # This function does all of the work of adding lookahead information for use + # with LALR parsing + # ----------------------------------------------------------------------------- + + def add_lalr_lookaheads(self, C): + # Determine all of the nullable nonterminals + nullable = self.compute_nullable_nonterminals() + + # Find all non-terminal transitions + trans = self.find_nonterminal_transitions(C) + + # Compute read sets + readsets = self.compute_read_sets(C, trans, nullable) + + # Compute lookback/includes relations + lookd, included = self.compute_lookback_includes(C, trans, nullable) + + # Compute LALR FOLLOW sets + followsets = self.compute_follow_sets(trans, readsets, included) + + # Add all of the lookaheads + self.add_lookaheads(lookd, followsets) + + # ----------------------------------------------------------------------------- + # lr_parse_table() + # + # This function constructs the parse tables for SLR or LALR + # ----------------------------------------------------------------------------- + def lr_parse_table(self): + Productions = self.grammar.Productions + Precedence = self.grammar.Precedence + goto = self.lr_goto # Goto array + action = self.lr_action # Action array + log = self.log # Logger for output + + actionp = {} # Action production array (temporary) + + log.info('Parsing method: %s', self.lr_method) + + # Step 1: Construct C = { I0, I1, ... IN}, collection of LR(0) items + # This determines the number of states + + C = self.lr0_items() + + if self.lr_method == 'LALR': + self.add_lalr_lookaheads(C) + + # Build the parser table, state by state + st = 0 + for I in C: + # Loop over each production in I + actlist = [] # List of actions + st_action = {} + st_actionp = {} + st_goto = {} + log.info('') + log.info('state %d', st) + log.info('') + for p in I: + log.info(' (%d) %s', p.number, p) + log.info('') + + for p in I: + if p.len == p.lr_index + 1: + if p.name == "S'": + # Start symbol. Accept! + st_action['$end'] = 0 + st_actionp['$end'] = p + else: + # We are at the end of a production. Reduce! + if self.lr_method == 'LALR': + laheads = p.lookaheads[st] + else: + laheads = self.grammar.Follow[p.name] + for a in laheads: + actlist.append((a, p, 'reduce using rule %d (%s)' % (p.number, p))) + r = st_action.get(a) + if r is not None: + # Whoa. Have a shift/reduce or reduce/reduce conflict + if r > 0: + # Need to decide on shift or reduce here + # By default we favor shifting. Need to add + # some precedence rules here. + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from rule being reduced (p) + rprec, rlevel = Productions[p.number].prec + + if (slevel < rlevel) or ((slevel == rlevel) and (rprec == 'left')): + # We really need to reduce here. + st_action[a] = -p.number + st_actionp[a] = p + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + Productions[p.number].reduced += 1 + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the shift + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif r < 0: + # Reduce/reduce conflict. In this case, we favor the rule + # that was defined first in the grammar file + oldp = Productions[-r] + pp = Productions[p.number] + if oldp.line > pp.line: + st_action[a] = -p.number + st_actionp[a] = p + chosenp, rejectp = pp, oldp + Productions[p.number].reduced += 1 + Productions[oldp.number].reduced -= 1 + else: + chosenp, rejectp = oldp, pp + self.rr_conflicts.append((st, chosenp, rejectp)) + log.info(' ! reduce/reduce conflict for %s resolved using rule %d (%s)', + a, st_actionp[a].number, st_actionp[a]) + else: + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = -p.number + st_actionp[a] = p + Productions[p.number].reduced += 1 + else: + i = p.lr_index + a = p.prod[i+1] # Get symbol right after the "." + if a in self.grammar.Terminals: + g = self.lr0_goto(I, a) + j = self.lr0_cidhash.get(id(g), -1) + if j >= 0: + # We are in a shift state + actlist.append((a, p, 'shift and go to state %d' % j)) + r = st_action.get(a) + if r is not None: + # Whoa have a shift/reduce or shift/shift conflict + if r > 0: + if r != j: + raise LALRError('Shift/shift conflict in state %d' % st) + elif r < 0: + # Do a precedence check. + # - if precedence of reduce rule is higher, we reduce. + # - if precedence of reduce is same and left assoc, we reduce. + # - otherwise we shift + + # Shift precedence comes from the token + sprec, slevel = Precedence.get(a, ('right', 0)) + + # Reduce precedence comes from the rule that could have been reduced + rprec, rlevel = Productions[st_actionp[a].number].prec + + if (slevel > rlevel) or ((slevel == rlevel) and (rprec == 'right')): + # We decide to shift here... highest precedence to shift + Productions[st_actionp[a].number].reduced -= 1 + st_action[a] = j + st_actionp[a] = p + if not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as shift', a) + self.sr_conflicts.append((st, a, 'shift')) + elif (slevel == rlevel) and (rprec == 'nonassoc'): + st_action[a] = None + else: + # Hmmm. Guess we'll keep the reduce + if not slevel and not rlevel: + log.info(' ! shift/reduce conflict for %s resolved as reduce', a) + self.sr_conflicts.append((st, a, 'reduce')) + + else: + raise LALRError('Unknown conflict in state %d' % st) + else: + st_action[a] = j + st_actionp[a] = p + + # Print the actions associated with each terminal + _actprint = {} + for a, p, m in actlist: + if a in st_action: + if p is st_actionp[a]: + log.info(' %-15s %s', a, m) + _actprint[(a, m)] = 1 + log.info('') + # Print the actions that were not used. (debugging) + not_used = 0 + for a, p, m in actlist: + if a in st_action: + if p is not st_actionp[a]: + if not (a, m) in _actprint: + log.debug(' ! %-15s [ %s ]', a, m) + not_used = 1 + _actprint[(a, m)] = 1 + if not_used: + log.debug('') + + # Construct the goto table for this state + + nkeys = {} + for ii in I: + for s in ii.usyms: + if s in self.grammar.Nonterminals: + nkeys[s] = None + for n in nkeys: + g = self.lr0_goto(I, n) + j = self.lr0_cidhash.get(id(g), -1) + if j >= 0: + st_goto[n] = j + log.info(' %-30s shift and go to state %d', n, j) + + action[st] = st_action + actionp[st] = st_actionp + goto[st] = st_goto + st += 1 + + # ----------------------------------------------------------------------------- + # write() + # + # This function writes the LR parsing tables to a file + # ----------------------------------------------------------------------------- + + def write_table(self, tabmodule, outputdir='', signature=''): + if isinstance(tabmodule, types.ModuleType): + raise IOError("Won't overwrite existing tabmodule") + + basemodulename = tabmodule.split('.')[-1] + filename = os.path.join(outputdir, basemodulename) + '.py' + try: + f = open(filename, 'w') + + f.write(''' +# %s +# This file is automatically generated. Do not edit. +_tabversion = %r + +_lr_method = %r + +_lr_signature = %r + ''' % (os.path.basename(filename), __tabversion__, self.lr_method, signature)) + + # Change smaller to 0 to go back to original tables + smaller = 1 + + # Factor out names to try and make smaller + if smaller: + items = {} + + for s, nd in self.lr_action.items(): + for name, v in nd.items(): + i = items.get(name) + if not i: + i = ([], []) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write('\n_lr_action_items = {') + for k, v in items.items(): + f.write('%r:([' % k) + for i in v[0]: + f.write('%r,' % i) + f.write('],[') + for i in v[1]: + f.write('%r,' % i) + + f.write(']),') + f.write('}\n') + + f.write(''' +_lr_action = {} +for _k, _v in _lr_action_items.items(): + for _x,_y in zip(_v[0],_v[1]): + if not _x in _lr_action: _lr_action[_x] = {} + _lr_action[_x][_k] = _y +del _lr_action_items +''') + + else: + f.write('\n_lr_action = { ') + for k, v in self.lr_action.items(): + f.write('(%r,%r):%r,' % (k[0], k[1], v)) + f.write('}\n') + + if smaller: + # Factor out names to try and make smaller + items = {} + + for s, nd in self.lr_goto.items(): + for name, v in nd.items(): + i = items.get(name) + if not i: + i = ([], []) + items[name] = i + i[0].append(s) + i[1].append(v) + + f.write('\n_lr_goto_items = {') + for k, v in items.items(): + f.write('%r:([' % k) + for i in v[0]: + f.write('%r,' % i) + f.write('],[') + for i in v[1]: + f.write('%r,' % i) + + f.write(']),') + f.write('}\n') + + f.write(''' +_lr_goto = {} +for _k, _v in _lr_goto_items.items(): + for _x, _y in zip(_v[0], _v[1]): + if not _x in _lr_goto: _lr_goto[_x] = {} + _lr_goto[_x][_k] = _y +del _lr_goto_items +''') + else: + f.write('\n_lr_goto = { ') + for k, v in self.lr_goto.items(): + f.write('(%r,%r):%r,' % (k[0], k[1], v)) + f.write('}\n') + + # Write production table + f.write('_lr_productions = [\n') + for p in self.lr_productions: + if p.func: + f.write(' (%r,%r,%d,%r,%r,%d),\n' % (p.str, p.name, p.len, + p.func, os.path.basename(p.file), p.line)) + else: + f.write(' (%r,%r,%d,None,None,None),\n' % (str(p), p.name, p.len)) + f.write(']\n') + f.close() + + except IOError as e: + raise + + + # ----------------------------------------------------------------------------- + # pickle_table() + # + # This function pickles the LR parsing tables to a supplied file object + # ----------------------------------------------------------------------------- + + def pickle_table(self, filename, signature=''): + try: + import cPickle as pickle + except ImportError: + import pickle + with open(filename, 'wb') as outf: + pickle.dump(__tabversion__, outf, pickle_protocol) + pickle.dump(self.lr_method, outf, pickle_protocol) + pickle.dump(signature, outf, pickle_protocol) + pickle.dump(self.lr_action, outf, pickle_protocol) + pickle.dump(self.lr_goto, outf, pickle_protocol) + + outp = [] + for p in self.lr_productions: + if p.func: + outp.append((p.str, p.name, p.len, p.func, os.path.basename(p.file), p.line)) + else: + outp.append((str(p), p.name, p.len, None, None, None)) + pickle.dump(outp, outf, pickle_protocol) + +# ----------------------------------------------------------------------------- +# === INTROSPECTION === +# +# The following functions and classes are used to implement the PLY +# introspection features followed by the yacc() function itself. +# ----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- +# 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) + ldict = f.f_globals.copy() + if f.f_globals != f.f_locals: + ldict.update(f.f_locals) + return ldict + +# ----------------------------------------------------------------------------- +# parse_grammar() +# +# This takes a raw grammar rule string and parses it into production data +# ----------------------------------------------------------------------------- +def parse_grammar(doc, file, line): + grammar = [] + # Split the doc string into lines + pstrings = doc.splitlines() + lastp = None + dline = line + for ps in pstrings: + dline += 1 + p = ps.split() + if not p: + continue + try: + if p[0] == '|': + # This is a continuation of a previous rule + if not lastp: + raise SyntaxError("%s:%d: Misplaced '|'" % (file, dline)) + prodname = lastp + syms = p[1:] + else: + prodname = p[0] + lastp = prodname + syms = p[2:] + assign = p[1] + if assign != ':' and assign != '::=': + raise SyntaxError("%s:%d: Syntax error. Expected ':'" % (file, dline)) + + grammar.append((file, dline, prodname, syms)) + except SyntaxError: + raise + except Exception: + raise SyntaxError('%s:%d: Syntax error in rule %r' % (file, dline, ps.strip())) + + return grammar + +# ----------------------------------------------------------------------------- +# ParserReflect() +# +# This class represents information extracted for building a parser including +# start symbol, error function, tokens, precedence list, action functions, +# etc. +# ----------------------------------------------------------------------------- +class ParserReflect(object): + def __init__(self, pdict, log=None): + self.pdict = pdict + self.start = None + self.error_func = None + self.tokens = None + self.modules = set() + self.grammar = [] + self.error = False + + if log is None: + self.log = PlyLogger(sys.stderr) + else: + self.log = log + + # Get all of the basic information + def get_all(self): + self.get_start() + self.get_error_func() + self.get_tokens() + self.get_precedence() + self.get_pfunctions() + + # Validate all of the information + def validate_all(self): + self.validate_start() + self.validate_error_func() + self.validate_tokens() + self.validate_precedence() + self.validate_pfunctions() + self.validate_modules() + return self.error + + # Compute a signature over the grammar + def signature(self): + parts = [] + try: + if self.start: + parts.append(self.start) + if self.prec: + parts.append(''.join([''.join(p) for p in self.prec])) + if self.tokens: + parts.append(' '.join(self.tokens)) + for f in self.pfuncs: + if f[3]: + parts.append(f[3]) + except (TypeError, ValueError): + pass + return ''.join(parts) + + # ----------------------------------------------------------------------------- + # validate_modules() + # + # This method checks to see if there are duplicated p_rulename() functions + # in the parser module file. Without this function, it is really easy for + # users to make mistakes by cutting and pasting code fragments (and it's a real + # bugger to try and figure out why the resulting parser doesn't work). Therefore, + # we just do a little regular expression pattern matching of def statements + # to try and detect duplicates. + # ----------------------------------------------------------------------------- + + def validate_modules(self): + # Match def p_funcname( + fre = re.compile(r'\s*def\s+(p_[a-zA-Z_0-9]*)\(') + + for module in self.modules: + try: + lines, linen = inspect.getsourcelines(module) + except IOError: + continue + + counthash = {} + for linen, line in enumerate(lines): + linen += 1 + m = fre.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.warning('%s:%d: Function %s redefined. Previously defined on line %d', + filename, linen, name, prev) + + # Get the start symbol + def get_start(self): + self.start = self.pdict.get('start') + + # Validate the start symbol + def validate_start(self): + if self.start is not None: + if not isinstance(self.start, string_types): + self.log.error("'start' must be a string") + + # Look for error handler + def get_error_func(self): + self.error_func = self.pdict.get('p_error') + + # Validate the error function + def validate_error_func(self): + if self.error_func: + if isinstance(self.error_func, types.FunctionType): + ismethod = 0 + elif isinstance(self.error_func, types.MethodType): + ismethod = 1 + else: + self.log.error("'p_error' defined, but is not a function or method") + self.error = True + return + + eline = self.error_func.__code__.co_firstlineno + efile = self.error_func.__code__.co_filename + module = inspect.getmodule(self.error_func) + self.modules.add(module) + + argcount = self.error_func.__code__.co_argcount - ismethod + if argcount != 1: + self.log.error('%s:%d: p_error() requires 1 argument', efile, eline) + self.error = True + + # Get the tokens map + def get_tokens(self): + tokens = self.pdict.get('tokens') + 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): + # Validate the tokens. + if 'error' in self.tokens: + self.log.error("Illegal token name 'error'. Is a reserved word") + self.error = True + return + + terminals = set() + for n in self.tokens: + if n in terminals: + self.log.warning('Token %r multiply defined', n) + terminals.add(n) + + # Get the precedence map (if any) + def get_precedence(self): + self.prec = self.pdict.get('precedence') + + # Validate and parse the precedence map + def validate_precedence(self): + preclist = [] + if self.prec: + if not isinstance(self.prec, (list, tuple)): + self.log.error('precedence must be a list or tuple') + self.error = True + return + for level, p in enumerate(self.prec): + if not isinstance(p, (list, tuple)): + self.log.error('Bad precedence table') + self.error = True + return + + if len(p) < 2: + self.log.error('Malformed precedence entry %s. Must be (assoc, term, ..., term)', p) + self.error = True + return + assoc = p[0] + if not isinstance(assoc, string_types): + self.log.error('precedence associativity must be a string') + self.error = True + return + for term in p[1:]: + if not isinstance(term, string_types): + self.log.error('precedence items must be strings') + self.error = True + return + preclist.append((term, assoc, level+1)) + self.preclist = preclist + + # Get all p_functions from the grammar + def get_pfunctions(self): + p_functions = [] + for name, item in self.pdict.items(): + if not name.startswith('p_') or name == 'p_error': + continue + if isinstance(item, (types.FunctionType, types.MethodType)): + line = getattr(item, 'co_firstlineno', item.__code__.co_firstlineno) + module = inspect.getmodule(item) + p_functions.append((line, module, name, item.__doc__)) + + # Sort all of the actions by line number; make sure to stringify + # modules to make them sortable, since `line` may not uniquely sort all + # p functions + p_functions.sort(key=lambda p_function: ( + p_function[0], + str(p_function[1]), + p_function[2], + p_function[3])) + self.pfuncs = p_functions + + # Validate all of the p_functions + def validate_pfunctions(self): + grammar = [] + # Check for non-empty symbols + if len(self.pfuncs) == 0: + self.log.error('no rules of the form p_rulename are defined') + self.error = True + return + + for line, module, name, doc in self.pfuncs: + file = inspect.getsourcefile(module) + func = self.pdict[name] + if isinstance(func, types.MethodType): + reqargs = 2 + else: + reqargs = 1 + if func.__code__.co_argcount > reqargs: + self.log.error('%s:%d: Rule %r has too many arguments', file, line, func.__name__) + self.error = True + elif func.__code__.co_argcount < reqargs: + self.log.error('%s:%d: Rule %r requires an argument', file, line, func.__name__) + self.error = True + elif not func.__doc__: + self.log.warning('%s:%d: No documentation string specified in function %r (ignored)', + file, line, func.__name__) + else: + try: + parsed_g = parse_grammar(doc, file, line) + for g in parsed_g: + grammar.append((name, g)) + except SyntaxError as e: + self.log.error(str(e)) + self.error = True + + # Looks like a valid grammar rule + # Mark the file in which defined. + self.modules.add(module) + + # Secondary validation step that looks for p_ definitions that are not functions + # or functions that look like they might be grammar rules. + + for n, v in self.pdict.items(): + if n.startswith('p_') and isinstance(v, (types.FunctionType, types.MethodType)): + continue + if n.startswith('t_'): + continue + if n.startswith('p_') and n != 'p_error': + self.log.warning('%r not defined as a function', n) + if ((isinstance(v, types.FunctionType) and v.__code__.co_argcount == 1) or + (isinstance(v, types.MethodType) and v.__func__.__code__.co_argcount == 2)): + if v.__doc__: + try: + doc = v.__doc__.split(' ') + if doc[1] == ':': + self.log.warning('%s:%d: Possible grammar rule %r defined without p_ prefix', + v.__code__.co_filename, v.__code__.co_firstlineno, n) + except IndexError: + pass + + self.grammar = grammar + +# ----------------------------------------------------------------------------- +# yacc(module) +# +# Build a parser +# ----------------------------------------------------------------------------- + +def yacc(method='LALR', debug=yaccdebug, module=None, tabmodule=tab_module, start=None, + check_recursion=True, optimize=False, write_tables=True, debugfile=debug_file, + outputdir=None, debuglog=None, errorlog=None, picklefile=None): + + if tabmodule is None: + tabmodule = tab_module + + # Reference to the parsing method of the last built parser + global parse + + # If pickling is enabled, table files are not created + if picklefile: + write_tables = 0 + + if errorlog is None: + errorlog = PlyLogger(sys.stderr) + + # Get the module dictionary used for the parser + if module: + _items = [(k, getattr(module, k)) for k in dir(module)] + pdict = dict(_items) + # If no __file__ attribute is available, try to obtain it from the __module__ instead + if '__file__' not in pdict: + pdict['__file__'] = sys.modules[pdict['__module__']].__file__ + else: + pdict = get_caller_module_dict(2) + + if outputdir is None: + # If no output directory is set, the location of the output files + # is determined according to the following rules: + # - If tabmodule specifies a package, files go into that package directory + # - Otherwise, files go in the same directory as the specifying module + if isinstance(tabmodule, types.ModuleType): + srcfile = tabmodule.__file__ + else: + if '.' not in tabmodule: + srcfile = pdict['__file__'] + else: + parts = tabmodule.split('.') + pkgname = '.'.join(parts[:-1]) + exec('import %s' % pkgname) + srcfile = getattr(sys.modules[pkgname], '__file__', '') + outputdir = os.path.dirname(srcfile) + + # Determine if the module is package of a package or not. + # If so, fix the tabmodule setting so that tables load correctly + pkg = pdict.get('__package__') + if pkg and isinstance(tabmodule, str): + if '.' not in tabmodule: + tabmodule = pkg + '.' + tabmodule + + + + # Set start symbol if it's specified directly using an argument + if start is not None: + pdict['start'] = start + + # Collect parser information from the dictionary + pinfo = ParserReflect(pdict, log=errorlog) + pinfo.get_all() + + if pinfo.error: + raise YaccError('Unable to build parser') + + # Check signature against table files (if any) + signature = pinfo.signature() + + # Read the tables + try: + lr = LRTable() + if picklefile: + read_signature = lr.read_pickle(picklefile) + else: + read_signature = lr.read_table(tabmodule) + if optimize or (read_signature == signature): + try: + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr, pinfo.error_func) + parse = parser.parse + return parser + except Exception as e: + errorlog.warning('There was a problem loading the table file: %r', e) + except VersionError as e: + errorlog.warning(str(e)) + except ImportError: + pass + + if debuglog is None: + if debug: + try: + debuglog = PlyLogger(open(os.path.join(outputdir, debugfile), 'w')) + except IOError as e: + errorlog.warning("Couldn't open %r. %s" % (debugfile, e)) + debuglog = NullLogger() + else: + debuglog = NullLogger() + + debuglog.info('Created by PLY version %s (http://www.dabeaz.com/ply)', __version__) + + errors = False + + # Validate the parser information + if pinfo.validate_all(): + raise YaccError('Unable to build parser') + + if not pinfo.error_func: + errorlog.warning('no p_error() function is defined') + + # Create a grammar object + grammar = Grammar(pinfo.tokens) + + # Set precedence level for terminals + for term, assoc, level in pinfo.preclist: + try: + grammar.set_precedence(term, assoc, level) + except GrammarError as e: + errorlog.warning('%s', e) + + # Add productions to the grammar + for funcname, gram in pinfo.grammar: + file, line, prodname, syms = gram + try: + grammar.add_production(prodname, syms, funcname, file, line) + except GrammarError as e: + errorlog.error('%s', e) + errors = True + + # Set the grammar start symbols + try: + if start is None: + grammar.set_start(pinfo.start) + else: + grammar.set_start(start) + except GrammarError as e: + errorlog.error(str(e)) + errors = True + + if errors: + raise YaccError('Unable to build parser') + + # Verify the grammar structure + undefined_symbols = grammar.undefined_symbols() + for sym, prod in undefined_symbols: + errorlog.error('%s:%d: Symbol %r used, but not defined as a token or a rule', prod.file, prod.line, sym) + errors = True + + unused_terminals = grammar.unused_terminals() + if unused_terminals: + debuglog.info('') + debuglog.info('Unused terminals:') + debuglog.info('') + for term in unused_terminals: + errorlog.warning('Token %r defined, but not used', term) + debuglog.info(' %s', term) + + # Print out all productions to the debug log + if debug: + debuglog.info('') + debuglog.info('Grammar') + debuglog.info('') + for n, p in enumerate(grammar.Productions): + debuglog.info('Rule %-5d %s', n, p) + + # Find unused non-terminals + unused_rules = grammar.unused_rules() + for prod in unused_rules: + errorlog.warning('%s:%d: Rule %r defined, but not used', prod.file, prod.line, prod.name) + + if len(unused_terminals) == 1: + errorlog.warning('There is 1 unused token') + if len(unused_terminals) > 1: + errorlog.warning('There are %d unused tokens', len(unused_terminals)) + + if len(unused_rules) == 1: + errorlog.warning('There is 1 unused rule') + if len(unused_rules) > 1: + errorlog.warning('There are %d unused rules', len(unused_rules)) + + if debug: + debuglog.info('') + debuglog.info('Terminals, with rules where they appear') + debuglog.info('') + terms = list(grammar.Terminals) + terms.sort() + for term in terms: + debuglog.info('%-20s : %s', term, ' '.join([str(s) for s in grammar.Terminals[term]])) + + debuglog.info('') + debuglog.info('Nonterminals, with rules where they appear') + debuglog.info('') + nonterms = list(grammar.Nonterminals) + nonterms.sort() + for nonterm in nonterms: + debuglog.info('%-20s : %s', nonterm, ' '.join([str(s) for s in grammar.Nonterminals[nonterm]])) + debuglog.info('') + + if check_recursion: + unreachable = grammar.find_unreachable() + for u in unreachable: + errorlog.warning('Symbol %r is unreachable', u) + + infinite = grammar.infinite_cycles() + for inf in infinite: + errorlog.error('Infinite recursion detected for symbol %r', inf) + errors = True + + unused_prec = grammar.unused_precedence() + for term, assoc in unused_prec: + errorlog.error('Precedence rule %r defined for unknown symbol %r', assoc, term) + errors = True + + if errors: + raise YaccError('Unable to build parser') + + # Run the LRGeneratedTable on the grammar + if debug: + errorlog.debug('Generating %s tables', method) + + lr = LRGeneratedTable(grammar, method, debuglog) + + if debug: + num_sr = len(lr.sr_conflicts) + + # Report shift/reduce and reduce/reduce conflicts + if num_sr == 1: + errorlog.warning('1 shift/reduce conflict') + elif num_sr > 1: + errorlog.warning('%d shift/reduce conflicts', num_sr) + + num_rr = len(lr.rr_conflicts) + if num_rr == 1: + errorlog.warning('1 reduce/reduce conflict') + elif num_rr > 1: + errorlog.warning('%d reduce/reduce conflicts', num_rr) + + # Write out conflicts to the output file + if debug and (lr.sr_conflicts or lr.rr_conflicts): + debuglog.warning('') + debuglog.warning('Conflicts:') + debuglog.warning('') + + for state, tok, resolution in lr.sr_conflicts: + debuglog.warning('shift/reduce conflict for %s in state %d resolved as %s', tok, state, resolution) + + already_reported = set() + for state, rule, rejected in lr.rr_conflicts: + if (state, id(rule), id(rejected)) in already_reported: + continue + debuglog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + debuglog.warning('rejected rule (%s) in state %d', rejected, state) + errorlog.warning('reduce/reduce conflict in state %d resolved using rule (%s)', state, rule) + errorlog.warning('rejected rule (%s) in state %d', rejected, state) + already_reported.add((state, id(rule), id(rejected))) + + warned_never = [] + for state, rule, rejected in lr.rr_conflicts: + if not rejected.reduced and (rejected not in warned_never): + debuglog.warning('Rule (%s) is never reduced', rejected) + errorlog.warning('Rule (%s) is never reduced', rejected) + warned_never.append(rejected) + + # Write the table file if requested + if write_tables: + try: + lr.write_table(tabmodule, outputdir, signature) + except IOError as e: + errorlog.warning("Couldn't create %r. %s" % (tabmodule, e)) + + # Write a pickled version of the tables + if picklefile: + try: + lr.pickle_table(picklefile, signature) + except IOError as e: + errorlog.warning("Couldn't create %r. %s" % (picklefile, e)) + + # Build the parser + lr.bind_callables(pinfo.pdict) + parser = LRParser(lr, pinfo.error_func) + + parse = parser.parse + return parser diff --git a/pycparser/ply/ygen.py b/pycparser/ply/ygen.py new file mode 100644 index 0000000..acf5ca1 --- /dev/null +++ b/pycparser/ply/ygen.py @@ -0,0 +1,74 @@ +# ply: ygen.py +# +# This is a support program that auto-generates different versions of the YACC parsing +# function with different features removed for the purposes of performance. +# +# Users should edit the method LParser.parsedebug() in yacc.py. The source code +# for that method is then used to create the other methods. See the comments in +# yacc.py for further details. + +import os.path +import shutil + +def get_source_range(lines, tag): + srclines = enumerate(lines) + start_tag = '#--! %s-start' % tag + end_tag = '#--! %s-end' % tag + + for start_index, line in srclines: + if line.strip().startswith(start_tag): + break + + for end_index, line in srclines: + if line.strip().endswith(end_tag): + break + + return (start_index + 1, end_index) + +def filter_section(lines, tag): + filtered_lines = [] + include = True + tag_text = '#--! %s' % tag + for line in lines: + if line.strip().startswith(tag_text): + include = not include + elif include: + filtered_lines.append(line) + return filtered_lines + +def main(): + dirname = os.path.dirname(__file__) + shutil.copy2(os.path.join(dirname, 'yacc.py'), os.path.join(dirname, 'yacc.py.bak')) + with open(os.path.join(dirname, 'yacc.py'), 'r') as f: + lines = f.readlines() + + parse_start, parse_end = get_source_range(lines, 'parsedebug') + parseopt_start, parseopt_end = get_source_range(lines, 'parseopt') + parseopt_notrack_start, parseopt_notrack_end = get_source_range(lines, 'parseopt-notrack') + + # Get the original source + orig_lines = lines[parse_start:parse_end] + + # Filter the DEBUG sections out + parseopt_lines = filter_section(orig_lines, 'DEBUG') + + # Filter the TRACKING sections out + parseopt_notrack_lines = filter_section(parseopt_lines, 'TRACKING') + + # Replace the parser source sections with updated versions + lines[parseopt_notrack_start:parseopt_notrack_end] = parseopt_notrack_lines + lines[parseopt_start:parseopt_end] = parseopt_lines + + lines = [line.rstrip()+'\n' for line in lines] + with open(os.path.join(dirname, 'yacc.py'), 'w') as f: + f.writelines(lines) + + print('Updated yacc.py') + +if __name__ == '__main__': + main() + + + + + diff --git a/pycparser/plyparser.py b/pycparser/plyparser.py new file mode 100644 index 0000000..6222c0e --- /dev/null +++ b/pycparser/plyparser.py @@ -0,0 +1,133 @@ +#----------------------------------------------------------------- +# plyparser.py +# +# PLYParser class and other utilites for simplifying programming +# parsers with PLY +# +# Eli Bendersky [https://eli.thegreenplace.net/] +# License: BSD +#----------------------------------------------------------------- + +import warnings + +class Coord(object): + """ Coordinates of a syntactic element. Consists of: + - File name + - Line number + - (optional) column number, for the Lexer + """ + __slots__ = ('file', 'line', 'column', '__weakref__') + def __init__(self, file, line, column=None): + self.file = file + self.line = line + self.column = column + + def __str__(self): + str = "%s:%s" % (self.file, self.line) + if self.column: str += ":%s" % self.column + return str + + +class ParseError(Exception): pass + + +class PLYParser(object): + def _create_opt_rule(self, rulename): + """ Given a rule name, creates an optional ply.yacc rule + for it. The name of the optional rule is + <rulename>_opt + """ + optname = rulename + '_opt' + + def optrule(self, p): + p[0] = p[1] + + optrule.__doc__ = '%s : empty\n| %s' % (optname, rulename) + optrule.__name__ = 'p_%s' % optname + setattr(self.__class__, optrule.__name__, optrule) + + def _coord(self, lineno, column=None): + return Coord( + file=self.clex.filename, + line=lineno, + column=column) + + def _token_coord(self, p, token_idx): + """ Returns the coordinates for the YaccProduction objet 'p' indexed + with 'token_idx'. The coordinate includes the 'lineno' and + 'column'. Both follow the lex semantic, starting from 1. + """ + last_cr = p.lexer.lexer.lexdata.rfind('\n', 0, p.lexpos(token_idx)) + if last_cr < 0: + last_cr = -1 + column = (p.lexpos(token_idx) - (last_cr)) + return self._coord(p.lineno(token_idx), column) + + def _parse_error(self, msg, coord): + raise ParseError("%s: %s" % (coord, msg)) + + +def parameterized(*params): + """ Decorator to create parameterized rules. + + Parameterized rule methods must be named starting with 'p_' and contain + 'xxx', and their docstrings may contain 'xxx' and 'yyy'. These will be + replaced by the given parameter tuples. For example, ``p_xxx_rule()`` with + docstring 'xxx_rule : yyy' when decorated with + ``@parameterized(('id', 'ID'))`` produces ``p_id_rule()`` with the docstring + 'id_rule : ID'. Using multiple tuples produces multiple rules. + """ + def decorate(rule_func): + rule_func._params = params + return rule_func + return decorate + + +def template(cls): + """ Class decorator to generate rules from parameterized rule templates. + + See `parameterized` for more information on parameterized rules. + """ + issued_nodoc_warning = False + for attr_name in dir(cls): + if attr_name.startswith('p_'): + method = getattr(cls, attr_name) + if hasattr(method, '_params'): + # Remove the template method + delattr(cls, attr_name) + # Create parameterized rules from this method; only run this if + # the method has a docstring. This is to address an issue when + # pycparser's users are installed in -OO mode which strips + # docstrings away. + # See: https://github.com/eliben/pycparser/pull/198/ and + # https://github.com/eliben/pycparser/issues/197 + # for discussion. + if method.__doc__ is not None: + _create_param_rules(cls, method) + elif not issued_nodoc_warning: + warnings.warn( + 'parsing methods must have __doc__ for pycparser to work properly', + RuntimeWarning, + stacklevel=2) + issued_nodoc_warning = True + return cls + + +def _create_param_rules(cls, func): + """ Create ply.yacc rules based on a parameterized rule function + + Generates new methods (one per each pair of parameters) based on the + template rule function `func`, and attaches them to `cls`. The rule + function's parameters must be accessible via its `_params` attribute. + """ + for xxx, yyy in func._params: + # Use the template method's body for each new method + def param_rule(self, p): + func(self, p) + + # Substitute in the params for the grammar rule and function name + param_rule.__doc__ = func.__doc__.replace('xxx', xxx).replace('yyy', yyy) + param_rule.__name__ = func.__name__.replace('xxx', xxx) + + # Attach the new method to the class + setattr(cls, param_rule.__name__, param_rule) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ed8a958 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[metadata] +license_file = LICENSE diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..62eddc2 --- /dev/null +++ b/setup.py @@ -0,0 +1,66 @@ +import os, sys +try: + from setuptools import setup + from setuptools.command.install import install as _install + from setuptools.command.sdist import sdist as _sdist +except ImportError: + from distutils.core import setup + from distutils.command.install import install as _install + from distutils.command.sdist import sdist as _sdist + + +def _run_build_tables(dir): + from subprocess import check_call + # This is run inside the install staging directory (that had no .pyc files) + # We don't want to generate any. + # https://github.com/eliben/pycparser/pull/135 + check_call([sys.executable, '-B', '_build_tables.py'], + cwd=os.path.join(dir, 'pycparser')) + + +class install(_install): + def run(self): + _install.run(self) + self.execute(_run_build_tables, (self.install_lib,), + msg="Build the lexing/parsing tables") + + +class sdist(_sdist): + def make_release_tree(self, basedir, files): + _sdist.make_release_tree(self, basedir, files) + self.execute(_run_build_tables, (basedir,), + msg="Build the lexing/parsing tables") + + +setup( + # metadata + name='pycparser', + description='C parser in Python', + long_description=""" + pycparser is a complete parser of the C language, written in + pure Python using the PLY parsing library. + It parses C code into an AST and can serve as a front-end for + C compilers or analysis tools. + """, + license='BSD', + version='2.19', + author='Eli Bendersky', + maintainer='Eli Bendersky', + author_email='eliben@gmail.com', + url='https://github.com/eliben/pycparser', + platforms='Cross Platform', + classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ], + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", + packages=['pycparser', 'pycparser.ply'], + package_data={'pycparser': ['*.cfg']}, + cmdclass={'install': install, 'sdist': sdist}, +) diff --git a/tests/README.txt b/tests/README.txt new file mode 100644 index 0000000..5196024 --- /dev/null +++ b/tests/README.txt @@ -0,0 +1 @@ +Run 'python tests/all_tests.py' from the root pycparser directory diff --git a/tests/all_tests.py b/tests/all_tests.py new file mode 100755 index 0000000..74761b6 --- /dev/null +++ b/tests/all_tests.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import sys +sys.path[0:0] = ['.', '..'] + +import unittest + + +suite = unittest.TestLoader().loadTestsFromNames( + [ + 'test_c_lexer', + 'test_c_ast', + 'test_general', + 'test_c_parser', + 'test_c_generator', + ] +) + +testresult = unittest.TextTestRunner(verbosity=1).run(suite) +sys.exit(0 if testresult.wasSuccessful() else 1) diff --git a/tests/c_files/cppd_with_stdio_h.c b/tests/c_files/cppd_with_stdio_h.c new file mode 100644 index 0000000..ab1426a --- /dev/null +++ b/tests/c_files/cppd_with_stdio_h.c @@ -0,0 +1,5038 @@ +#line 1 "example_c_file.c"
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+#line 19 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+#line 25 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/newlib.h"
+
+#line 3 "D:\eli\cpp_stuff\libc_include/newlib.h"
+#line 16 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+
+
+
+#line 52 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+
+
+#line 58 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 83 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+#line 86 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+#line 89 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+
+#line 95 "D:\eli\cpp_stuff\libc_include/machine/ieeefp.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 5 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 143 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 157 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 195 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+#line 207 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+#line 17 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+#line 21 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 30 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 19 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 26 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 30 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 35 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 39 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 42 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+#line 53 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 56 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 67 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+#line 76 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 98 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+#line 108 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 126 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 131 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 170 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef long unsigned int size_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 243 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 246 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 290 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 302 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+#line 310 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 361 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+#line 365 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 418 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 422 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 427 "D:\eli\cpp_stuff\libc_include/stddef.h"
+#line 35 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+#line 19 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+#line 26 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+#line 30 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef char* __builtin_va_list;
+typedef __builtin_va_list __gnuc_va_list;
+
+
+
+#line 50 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+
+
+
+
+
+
+
+
+
+
+#line 66 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+
+
+
+
+
+
+
+
+
+#line 80 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 98 "D:\eli\cpp_stuff\libc_include/stdarg.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 38 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+#line 44 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 6 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+
+
+
+
+
+#line 21 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 14 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+#line 8 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/machine/_types.h"
+
+#line 4 "D:\eli\cpp_stuff\libc_include/machine/_types.h"
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/machine/_default_types.h"
+
+#line 4 "D:\eli\cpp_stuff\libc_include/machine/_default_types.h"
+
+
+
+
+
+
+
+
+
+#line 15 "D:\eli\cpp_stuff\libc_include/machine/_default_types.h"
+
+#line 17 "D:\eli\cpp_stuff\libc_include/machine/_default_types.h"
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/limits.h"
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/newlib.h"
+
+#line 3 "D:\eli\cpp_stuff\libc_include/newlib.h"
+#line 5 "D:\eli\cpp_stuff\libc_include/limits.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 19 "D:\eli\cpp_stuff\libc_include/limits.h"
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 143 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 157 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 195 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+
+
+
+
+#line 207 "D:\eli\cpp_stuff\libc_include/sys/config.h"
+
+
+
+
+
+
+
+#line 25 "D:\eli\cpp_stuff\libc_include/limits.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 79 "D:\eli\cpp_stuff\libc_include/limits.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 23 "D:\eli\cpp_stuff\libc_include/machine/_default_types.h"
+
+
+
+typedef signed char __int8_t ;
+typedef unsigned char __uint8_t ;
+
+
+
+
+
+
+
+
+typedef signed short __int16_t;
+typedef unsigned short __uint16_t;
+
+
+
+
+
+
+
+
+typedef __int16_t __int_least16_t;
+typedef __uint16_t __uint_least16_t;
+
+
+
+
+
+
+
+
+
+
+typedef signed int __int32_t;
+typedef unsigned int __uint32_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef __int32_t __int_least32_t;
+typedef __uint32_t __uint_least32_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 8 "D:\eli\cpp_stuff\libc_include/machine/_types.h"
+
+#line 13 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/lock.h"
+
+
+
+
+
+typedef int _LOCK_T;
+typedef int _LOCK_RECURSIVE_T;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 14 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+typedef long _off_t;
+
+
+
+
+
+
+
+typedef short __dev_t;
+
+
+
+
+typedef unsigned short __uid_t;
+
+
+typedef unsigned short __gid_t;
+
+
+
+ typedef long long _off64_t;
+
+
+
+#line 43 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+typedef long _fpos_t;
+
+
+
+
+
+
+
+
+
+
+
+typedef int _ssize_t;
+
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 19 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 26 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 30 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 35 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 39 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 42 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+#line 53 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 56 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 67 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+#line 76 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 98 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+#line 108 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 126 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 131 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 170 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 243 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 246 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 290 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 302 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+#line 310 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned int wint_t;
+
+
+
+
+
+#line 361 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+#line 365 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 418 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 422 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 427 "D:\eli\cpp_stuff\libc_include/stddef.h"
+#line 64 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+typedef struct
+{
+ int __count;
+ union
+ {
+ wint_t __wch;
+ unsigned char __wchb[4];
+ } __value;
+} _mbstate_t;
+
+
+
+typedef _LOCK_RECURSIVE_T _flock_t;
+
+
+
+
+typedef void *_iconv_t;
+
+
+
+#line 15 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+typedef unsigned long __ULong;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+struct _reent;
+
+
+#line 43 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+struct _Bigint
+{
+ struct _Bigint *_next;
+ int _k, _maxwds, _sign, _wds;
+ __ULong _x[1];
+};
+
+
+struct __tm
+{
+ int __tm_sec;
+ int __tm_min;
+ int __tm_hour;
+ int __tm_mday;
+ int __tm_mon;
+ int __tm_year;
+ int __tm_wday;
+ int __tm_yday;
+ int __tm_isdst;
+};
+
+
+#line 68 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+struct _on_exit_args {
+ void * _fnargs[32];
+ void * _dso_handle[32];
+
+ __ULong _fntypes;
+#line 77 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+ __ULong _is_cxa;
+};
+
+
+
+
+
+
+
+
+
+struct _atexit {
+ struct _atexit *_next;
+ int _ind;
+
+ void (*_fns[32])(void);
+ struct _on_exit_args _on_exit_args;
+};
+
+
+
+#line 104 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+struct __sbuf {
+ unsigned char *_base;
+ int _size;
+};
+
+
+#line 134 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 141 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+struct __sFILE {
+ unsigned char *_p;
+ int _r;
+ int _w;
+ short _flags;
+ short _file;
+ struct __sbuf _bf;
+ int _lbfsize;
+
+
+
+
+
+
+ char * _cookie;
+
+ int(*_read)();
+#line 176 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+ int(*_write)();
+#line 178 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+ _fpos_t(*_seek)();
+ int(*_close)();
+
+
+ struct __sbuf _ub;
+ unsigned char *_up;
+ int _ur;
+
+
+ unsigned char _ubuf[3];
+ unsigned char _nbuf[1];
+
+
+ struct __sbuf _lb;
+
+
+ int _blksize;
+ int _offset;
+
+
+ struct _reent *_data;
+
+
+
+ _flock_t _lock;
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef struct __sFILE __FILE;
+
+
+
+struct _glue
+{
+ struct _glue *_next;
+ int _niobs;
+ __FILE *_iobs;
+};
+
+
+#line 284 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+struct _rand48 {
+ unsigned short _seed[3];
+ unsigned short _mult[3];
+ unsigned short _add;
+
+
+
+
+};
+
+
+
+
+
+
+
+#line 313 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 344 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+#line 350 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 420 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 452 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 474 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 478 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 482 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 494 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 496 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 503 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 505 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 508 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 531 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 533 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 536 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+struct _reent
+{
+ int _errno;
+
+
+#line 571 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+ __FILE *_stdin, *_stdout, *_stderr;
+
+ int _inc;
+ char _emergency[25];
+
+ int _current_category;
+ char *_current_locale;
+
+ int __sdidinit;
+
+ void(*__cleanup)();
+
+
+ struct _Bigint *_result;
+ int _result_k;
+ struct _Bigint *_p5s;
+ struct _Bigint **_freelist;
+
+
+ int _cvtlen;
+ char *_cvtbuf;
+
+ union
+ {
+ struct
+ {
+ unsigned int _unused_rand;
+ char * _strtok_last;
+ char _asctime_buf[26];
+ struct __tm _localtime_buf;
+ int _gamma_signgam;
+ unsigned long long _rand_next;
+ struct _rand48 _r48;
+ _mbstate_t _mblen_state;
+ _mbstate_t _mbtowc_state;
+ _mbstate_t _wctomb_state;
+ char _l64a_buf[8];
+ char _signal_buf[24];
+ int _getdate_err;
+ _mbstate_t _mbrlen_state;
+ _mbstate_t _mbrtowc_state;
+ _mbstate_t _mbsrtowcs_state;
+ _mbstate_t _wcrtomb_state;
+ _mbstate_t _wcsrtombs_state;
+ } _reent;
+
+#line 619 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+ struct
+ {
+
+ unsigned char * _nextf[30];
+ unsigned int _nmalloc[30];
+ } _unused;
+ } _new;
+
+
+ struct _atexit *_atexit;
+ struct _atexit _atexit0;
+
+
+ void (**(_sig_func))(int);
+
+
+#line 637 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+ struct _glue __sglue;
+ __FILE __sf[3];
+};
+
+
+#line 689 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 751 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 791 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+extern struct _reent *_impure_ptr;
+extern struct _reent * _global_impure_ptr;
+
+void _reclaim_reent();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 46 "D:\eli\cpp_stuff\libc_include/stdio.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+#line 17 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+
+
+
+
+
+#line 21 "D:\eli\cpp_stuff\libc_include/_ansi.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 21 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/machine/_types.h"
+
+#line 4 "D:\eli\cpp_stuff\libc_include/machine/_types.h"
+
+
+
+
+
+#line 26 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+#line 33 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+#line 8 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 43 "D:\eli\cpp_stuff\libc_include/sys/_types.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 62 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 19 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 26 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 30 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 35 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 39 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 42 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+#line 53 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 56 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 67 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+#line 76 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 98 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+#line 108 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 126 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 131 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef long int ptrdiff_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 170 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 243 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+#line 246 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 290 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+#line 302 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+#line 310 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef int wchar_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 361 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+#line 365 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 418 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+#line 422 "D:\eli\cpp_stuff\libc_include/stddef.h"
+
+
+
+
+#line 427 "D:\eli\cpp_stuff\libc_include/stddef.h"
+#line 70 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/machine/types.h"
+
+
+
+
+#line 9 "D:\eli\cpp_stuff\libc_include/machine/types.h"
+
+
+
+
+
+
+
+
+
+
+typedef long int __off_t;
+typedef int __pid_t;
+
+
+
+typedef long int __loff_t;
+
+
+
+
+
+
+#line 71 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+#line 79 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned char u_char;
+typedef unsigned short u_short;
+typedef unsigned int u_int;
+typedef unsigned long u_long;
+
+
+
+typedef unsigned short ushort;
+typedef unsigned int uint;
+
+
+
+typedef unsigned long clock_t;
+
+
+
+
+typedef long time_t;
+
+
+
+
+struct timespec {
+ time_t tv_sec;
+ long tv_nsec;
+};
+
+struct itimerspec {
+ struct timespec it_interval;
+ struct timespec it_value;
+};
+
+
+typedef long daddr_t;
+typedef char * caddr_t;
+
+
+
+#line 131 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+typedef unsigned short ino_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 160 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+typedef _off_t off_t;
+typedef __dev_t dev_t;
+typedef __uid_t uid_t;
+typedef __gid_t gid_t;
+
+
+typedef int pid_t;
+
+typedef long key_t;
+
+typedef _ssize_t ssize_t;
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned int mode_t;
+
+
+
+
+typedef unsigned short nlink_t;
+
+
+#line 200 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+#line 209 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+typedef long fd_mask;
+
+
+
+
+
+
+#line 221 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+typedef struct _types_fd_set {
+ fd_mask fds_bits[(((64)+(((sizeof (fd_mask) * 8))-1))/((sizeof (fd_mask) * 8)))];
+} _types_fd_set;
+
+
+
+
+
+
+
+#line 236 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+
+
+
+typedef unsigned long clockid_t;
+
+
+
+
+typedef unsigned long timer_t;
+
+
+
+typedef unsigned long useconds_t;
+typedef long suseconds_t;
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/features.h"
+
+#line 20 "D:\eli\cpp_stuff\libc_include/sys/features.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 257 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+#line 266 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+#line 273 "D:\eli\cpp_stuff\libc_include/sys/types.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 47 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+typedef __FILE FILE;
+
+
+
+
+
+
+
+
+typedef _fpos_t fpos_t;
+
+
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/stdio.h"
+
+
+
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/lock.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 5 "D:\eli\cpp_stuff\libc_include/sys/stdio.h"
+#line 1 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 6 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 43 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 68 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+#line 77 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 104 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+#line 134 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 141 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 284 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 313 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 344 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+#line 350 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 420 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 452 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 474 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 478 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 482 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+#line 494 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 496 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 503 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 505 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 508 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 531 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+#line 533 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 536 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 571 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 619 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 637 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+#line 689 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+#line 751 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 791 "D:\eli\cpp_stuff\libc_include/sys/reent.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 6 "D:\eli\cpp_stuff\libc_include/sys/stdio.h"
+
+
+#line 11 "D:\eli\cpp_stuff\libc_include/sys/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 66 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 96 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 163 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+FILE * tmpfile();
+char * tmpnam();
+int fclose();
+int fflush();
+FILE * freopen();
+void setbuf();
+int setvbuf();
+int fprintf();
+#line 179 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int fscanf();
+#line 181 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int printf();
+#line 183 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int scanf();
+#line 185 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int sscanf();
+#line 187 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vfprintf();
+#line 189 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vprintf();
+#line 191 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsprintf();
+#line 193 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int fgetc();
+char * fgets();
+int fputc();
+int fputs();
+int getc();
+int getchar();
+char * gets();
+int putc();
+int putchar();
+int puts();
+int ungetc();
+size_t fread();
+size_t fwrite();
+
+
+
+int fgetpos();
+
+int fseek();
+
+
+
+int fsetpos();
+
+long ftell();
+void rewind();
+void clearerr();
+int feof();
+int ferror();
+void perror();
+
+FILE * fopen();
+int sprintf();
+#line 227 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int remove();
+int rename();
+
+
+
+
+
+
+int fseeko();
+off_t ftello();
+
+
+int asiprintf();
+#line 241 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * asniprintf();
+#line 243 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * asnprintf();
+#line 245 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int asprintf();
+#line 247 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+int diprintf();
+#line 250 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+int fcloseall();
+int fiprintf();
+#line 254 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int fiscanf();
+#line 256 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int iprintf();
+#line 258 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int iscanf();
+#line 260 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int siprintf();
+#line 262 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int siscanf();
+#line 264 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int snprintf();
+#line 266 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int sniprintf();
+#line 268 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * tempnam();
+int vasiprintf();
+#line 271 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * vasniprintf();
+#line 273 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * vasnprintf();
+#line 275 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vasprintf();
+#line 277 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vdiprintf();
+#line 279 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vfiprintf();
+#line 281 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vfiscanf();
+#line 283 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vfscanf();
+#line 285 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int viprintf();
+#line 287 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int viscanf();
+#line 289 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vscanf();
+#line 291 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsiprintf();
+#line 293 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsiscanf();
+#line 295 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsniprintf();
+#line 297 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsnprintf();
+#line 299 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int vsscanf();
+#line 301 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 307 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+FILE * fdopen();
+
+int fileno();
+int getw();
+int pclose();
+FILE * popen();
+int putw();
+void setbuffer();
+int setlinebuf();
+int getc_unlocked();
+int getchar_unlocked();
+void flockfile();
+int ftrylockfile();
+void funlockfile();
+int putc_unlocked();
+int putchar_unlocked();
+
+
+
+#line 331 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+int dprintf();
+#line 337 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+FILE * fmemopen();
+
+
+FILE * open_memstream();
+
+int vdprintf();
+#line 345 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 351 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+int _asiprintf_r();
+#line 354 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * _asniprintf_r();
+#line 356 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * _asnprintf_r();
+#line 358 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _asprintf_r();
+#line 360 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _diprintf_r();
+#line 362 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _dprintf_r();
+#line 364 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _fclose_r();
+int _fcloseall_r();
+FILE * _fdopen_r();
+int _fflush_r();
+char * _fgets_r();
+int _fiprintf_r();
+#line 371 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _fiscanf_r();
+#line 373 "D:\eli\cpp_stuff\libc_include/stdio.h"
+FILE * _fmemopen_r();
+FILE * _fopen_r();
+int _fprintf_r();
+#line 377 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _fputc_r();
+int _fputs_r();
+size_t _fread_r();
+int _fscanf_r();
+#line 382 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _fseek_r();
+long _ftell_r();
+size_t _fwrite_r();
+int _getc_r();
+int _getc_unlocked_r();
+int _getchar_r();
+int _getchar_unlocked_r();
+char * _gets_r();
+int _iprintf_r();
+#line 392 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _iscanf_r();
+#line 394 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _mkstemp_r();
+char * _mktemp_r();
+FILE * _open_memstream_r();
+void _perror_r();
+int _printf_r();
+#line 400 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _putc_r();
+int _putc_unlocked_r();
+int _putchar_unlocked_r();
+int _putchar_r();
+int _puts_r();
+int _remove_r();
+int _rename_r();
+#line 408 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _scanf_r();
+#line 410 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _siprintf_r();
+#line 412 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _siscanf_r();
+#line 414 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _sniprintf_r();
+#line 416 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _snprintf_r();
+#line 418 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _sprintf_r();
+#line 420 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _sscanf_r();
+#line 422 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * _tempnam_r();
+FILE * _tmpfile_r();
+char * _tmpnam_r();
+int _ungetc_r();
+int _vasiprintf_r();
+#line 428 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * _vasniprintf_r();
+#line 430 "D:\eli\cpp_stuff\libc_include/stdio.h"
+char * _vasnprintf_r();
+#line 432 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vasprintf_r();
+#line 434 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vdiprintf_r();
+#line 436 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vdprintf_r();
+#line 438 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vfiprintf_r();
+#line 440 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vfiscanf_r();
+#line 442 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vfprintf_r();
+#line 444 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vfscanf_r();
+#line 446 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _viprintf_r();
+#line 448 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _viscanf_r();
+#line 450 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vprintf_r();
+#line 452 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vscanf_r();
+#line 454 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsiprintf_r();
+#line 456 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsiscanf_r();
+#line 458 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsniprintf_r();
+#line 460 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsnprintf_r();
+#line 462 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsprintf_r();
+#line 464 "D:\eli\cpp_stuff\libc_include/stdio.h"
+int _vsscanf_r();
+#line 466 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+ssize_t __getdelim();
+ssize_t __getline();
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 493 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+int __srget_r();
+int __swbuf_r();
+
+
+#line 500 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+FILE * funopen();
+#line 514 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+#line 518 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+#line 520 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+typedef ssize_t cookie_read_function_t(void *__cookie, char *__buf, size_t __n);
+typedef ssize_t cookie_write_function_t(void *__cookie, const char *__buf,
+ size_t __n);
+
+
+
+
+typedef int cookie_seek_function_t(void *__cookie, off_t *__off, int __whence);
+
+typedef int cookie_close_function_t(void *__cookie);
+typedef struct
+{
+
+#line 535 "D:\eli\cpp_stuff\libc_include/stdio.h"
+ cookie_read_function_t *read;
+ cookie_write_function_t *write;
+ cookie_seek_function_t *seek;
+ cookie_close_function_t *close;
+} cookie_io_functions_t;
+FILE * fopencookie();
+#line 542 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 549 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+#line 574 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+#line 580 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 603 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+#line 613 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+#line 621 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+#line 626 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 657 "D:\eli\cpp_stuff\libc_include/stdio.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#line 4 "example_c_file.c"
+
+#line 8 "example_c_file.c"
+
+char tav = 'b';
+char maav = L"'guruguru\n";
+char* moral = "ain't I \\\"\\\t\" a nice string?\"\"";
+char* comment_inside = "but you will /* see it */!!!!";
+char* i_have_newlines = "line one\nline two\nline three";
+
+int main()
+{
+ auto char* multi = "a multi";
+}
+
+
+
+
+
diff --git a/tests/c_files/empty.h b/tests/c_files/empty.h new file mode 100644 index 0000000..635750a --- /dev/null +++ b/tests/c_files/empty.h @@ -0,0 +1,8 @@ +#define PERFECTLY +#define NORMAL +#define TO +#define HAVE +#define HEADER +#define WITH +#define ONLY +#define DEFINES diff --git a/tests/c_files/example_c_file.c b/tests/c_files/example_c_file.c new file mode 100644 index 0000000..17b2ac4 --- /dev/null +++ b/tests/c_files/example_c_file.c @@ -0,0 +1,12 @@ +char tav = 'b';
+char* moral = "ain't I \\\"\\\t\" a nice string?\"\"";
+char* comment_inside = "but you will /* see it */!!!!";
+char* i_have_newlines = "line one\nline two\nline three";
+
+int main()
+{
+ auto char* multi = "a multi";
+}
+
+
+
diff --git a/tests/c_files/hdir/9/inc.h b/tests/c_files/hdir/9/inc.h new file mode 100644 index 0000000..0c96cbd --- /dev/null +++ b/tests/c_files/hdir/9/inc.h @@ -0,0 +1 @@ +extern int ie; diff --git a/tests/c_files/memmgr.c b/tests/c_files/memmgr.c new file mode 100644 index 0000000..6036ec6 --- /dev/null +++ b/tests/c_files/memmgr.c @@ -0,0 +1,206 @@ +//----------------------------------------------------------------
+// Statically-allocated memory manager
+//
+// by Eli Bendersky (eliben@gmail.com)
+//
+// This code is in the public domain.
+//----------------------------------------------------------------
+#include "memmgr.h"
+
+typedef ulong Align;
+
+union mem_header_union
+{
+ struct
+ {
+ // Pointer to the next block in the free list
+ //
+ union mem_header_union* next;
+
+ // Size of the block (in quantas of sizeof(mem_header_t))
+ //
+ ulong size;
+ } s;
+
+ // Used to align headers in memory to a boundary
+ //
+ Align align_dummy;
+};
+
+typedef union mem_header_union mem_header_t;
+
+// Initial empty list
+//
+static mem_header_t base;
+
+// Start of free list
+//
+static mem_header_t* freep = 0;
+
+// Static pool for new allocations
+//
+static byte pool[POOL_SIZE] = {0};
+static ulong pool_free_pos = 0;
+
+
+void memmgr_init()
+{
+ base.s.next = 0;
+ base.s.size = 0;
+ freep = 0;
+ pool_free_pos = 0;
+}
+
+
+static mem_header_t* get_mem_from_pool(ulong nquantas)
+{
+ ulong total_req_size;
+
+ mem_header_t* h;
+
+ if (nquantas < MIN_POOL_ALLOC_QUANTAS)
+ nquantas = MIN_POOL_ALLOC_QUANTAS;
+
+ total_req_size = nquantas * sizeof(mem_header_t);
+
+ if (pool_free_pos + total_req_size <= POOL_SIZE)
+ {
+ h = (mem_header_t*) (pool + pool_free_pos);
+ h->s.size = nquantas;
+ memmgr_free((void*) (h + 1));
+ pool_free_pos += total_req_size;
+ }
+ else
+ {
+ return 0;
+ }
+
+ return freep;
+}
+
+
+// Allocations are done in 'quantas' of header size.
+// The search for a free block of adequate size begins at the point 'freep'
+// where the last block was found.
+// If a too-big block is found, it is split and the tail is returned (this
+// way the header of the original needs only to have its size adjusted).
+// The pointer returned to the user points to the free space within the block,
+// which begins one quanta after the header.
+//
+void* memmgr_alloc(ulong nbytes)
+{
+ mem_header_t* p;
+ mem_header_t* prevp;
+
+ // Calculate how many quantas are required: we need enough to house all
+ // the requested bytes, plus the header. The -1 and +1 are there to make sure
+ // that if nbytes is a multiple of nquantas, we don't allocate too much
+ //
+ ulong nquantas = (nbytes + sizeof(mem_header_t) - 1) / sizeof(mem_header_t) + 1;
+
+ // First alloc call, and no free list yet ? Use 'base' for an initial
+ // denegerate block of size 0, which points to itself
+ //
+ if ((prevp = freep) == 0)
+ {
+ base.s.next = freep = prevp = &base;
+ base.s.size = 0;
+ }
+
+ for (p = prevp->s.next; ; prevp = p, p = p->s.next)
+ {
+ // big enough ?
+ if (p->s.size >= nquantas)
+ {
+ // exactly ?
+ if (p->s.size == nquantas)
+ {
+ // just eliminate this block from the free list by pointing
+ // its prev's next to its next
+ //
+ prevp->s.next = p->s.next;
+ }
+ else // too big
+ {
+ p->s.size -= nquantas;
+ p += p->s.size;
+ p->s.size = nquantas;
+ }
+
+ freep = prevp;
+ return (void*) (p + 1);
+ }
+ // Reached end of free list ?
+ // Try to allocate the block from the pool. If that succeeds,
+ // get_mem_from_pool adds the new block to the free list and
+ // it will be found in the following iterations. If the call
+ // to get_mem_from_pool doesn't succeed, we've run out of
+ // memory
+ //
+ else if (p == freep)
+ {
+ if ((p = get_mem_from_pool(nquantas)) == 0)
+ {
+ #ifdef DEBUG_MEMMGR_FATAL
+ printf("!! Memory allocation failed !!\n");
+ #endif
+ return 0;
+ }
+ }
+ }
+}
+
+
+// Scans the free list, starting at freep, looking the the place to insert the
+// free block. This is either between two existing blocks or at the end of the
+// list. In any case, if the block being freed is adjacent to either neighbor,
+// the adjacent blocks are combined.
+//
+void memmgr_free(void* ap)
+{
+ mem_header_t* block;
+ mem_header_t* p;
+
+ // acquire pointer to block header
+ block = ((mem_header_t*) ap) - 1;
+
+ // Find the correct place to place the block in (the free list is sorted by
+ // address, increasing order)
+ //
+ for (p = freep; !(block > p && block < p->s.next); p = p->s.next)
+ {
+ // Since the free list is circular, there is one link where a
+ // higher-addressed block points to a lower-addressed block.
+ // This condition checks if the block should be actually
+ // inserted between them
+ //
+ if (p >= p->s.next && (block > p || block < p->s.next))
+ break;
+ }
+
+ // Try to combine with the higher neighbor
+ //
+ if (block + block->s.size == p->s.next)
+ {
+ block->s.size += p->s.next->s.size;
+ block->s.next = p->s.next->s.next;
+ }
+ else
+ {
+ block->s.next = p->s.next;
+ }
+
+ // Try to combine with the lower neighbor
+ //
+ if (p + p->s.size == block)
+ {
+ p->s.size += block->s.size;
+ p->s.next = block->s.next;
+ }
+ else
+ {
+ p->s.next = block;
+ }
+
+ freep = p;
+}
diff --git a/tests/c_files/memmgr.h b/tests/c_files/memmgr.h new file mode 100644 index 0000000..ae8212d --- /dev/null +++ b/tests/c_files/memmgr.h @@ -0,0 +1,96 @@ +//----------------------------------------------------------------
+// Statically-allocated memory manager
+//
+// by Eli Bendersky (eliben@gmail.com)
+//
+// This code is in the public domain.
+//----------------------------------------------------------------
+#ifndef MEMMGR_H
+#define MEMMGR_H
+
+//
+// Memory manager: dynamically allocates memory from
+// a fixed pool that is allocated statically at link-time.
+//
+// Usage: after calling memmgr_init() in your
+// initialization routine, just use memmgr_alloc() instead
+// of malloc() and memmgr_free() instead of free().
+// Naturally, you can use the preprocessor to define
+// malloc() and free() as aliases to memmgr_alloc() and
+// memmgr_free(). This way the manager will be a drop-in
+// replacement for the standard C library allocators, and can
+// be useful for debugging memory allocation problems and
+// leaks.
+//
+// Preprocessor flags you can define to customize the
+// memory manager:
+//
+// DEBUG_MEMMGR_FATAL
+// Allow printing out a message when allocations fail
+//
+// DEBUG_MEMMGR_SUPPORT_STATS
+// Allow printing out of stats in function
+// memmgr_print_stats When this is disabled,
+// memmgr_print_stats does nothing.
+//
+// Note that in production code on an embedded system
+// you'll probably want to keep those undefined, because
+// they cause printf to be called.
+//
+// POOL_SIZE
+// Size of the pool for new allocations. This is
+// effectively the heap size of the application, and can
+// be changed in accordance with the available memory
+// resources.
+//
+// MIN_POOL_ALLOC_QUANTAS
+// Internally, the memory manager allocates memory in
+// quantas roughly the size of two ulong objects. To
+// minimize pool fragmentation in case of multiple allocations
+// and deallocations, it is advisable to not allocate
+// blocks that are too small.
+// This flag sets the minimal ammount of quantas for
+// an allocation. If the size of a ulong is 4 and you
+// set this flag to 16, the minimal size of an allocation
+// will be 4 * 2 * 16 = 128 bytes
+// If you have a lot of small allocations, keep this value
+// low to conserve memory. If you have mostly large
+// allocations, it is best to make it higher, to avoid
+// fragmentation.
+//
+// Notes:
+// 1. This memory manager is *not thread safe*. Use it only
+// for single thread/task applications.
+//
+
+#define DEBUG_MEMMGR_SUPPORT_STATS 1
+
+#define POOL_SIZE 8 * 1024
+#define MIN_POOL_ALLOC_QUANTAS 16
+
+
+typedef unsigned char byte;
+typedef unsigned long ulong;
+
+
+
+// Initialize the memory manager. This function should be called
+// only once in the beginning of the program.
+//
+void memmgr_init();
+
+// 'malloc' clone
+//
+void* memmgr_alloc(ulong nbytes);
+
+// 'free' clone
+//
+void memmgr_free(void* ap);
+
+// Prints statistics about the current state of the memory
+// manager
+//
+void memmgr_print_stats();
+
+
+#endif // MEMMGR_H
diff --git a/tests/c_files/memmgr_with_h.c b/tests/c_files/memmgr_with_h.c new file mode 100644 index 0000000..8ea6ff6 --- /dev/null +++ b/tests/c_files/memmgr_with_h.c @@ -0,0 +1,350 @@ +#line 1 "memmgr.c"
+
+
+
+
+
+
+
+#line 1 "./memmgr.h"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+typedef unsigned char byte;
+typedef unsigned long ulong;
+
+
+
+
+
+
+void memmgr_init();
+
+
+
+void* memmgr_alloc(ulong nbytes);
+
+
+
+void memmgr_free(void* ap);
+
+
+
+
+void memmgr_print_stats();
+
+
+
+#line 9 "memmgr.c"
+
+typedef ulong Align;
+
+union mem_header_union
+{
+ struct
+ {
+
+
+ union mem_header_union* next;
+
+
+
+ ulong size;
+ } s;
+
+
+
+ Align align_dummy;
+};
+
+typedef union mem_header_union mem_header_t;
+
+
+
+static mem_header_t base;
+
+
+
+static mem_header_t* freep = 0;
+
+
+
+static byte pool[8 * 1024] = {0};
+static ulong pool_free_pos = 0;
+
+
+void memmgr_init()
+{
+ base.s.next = 0;
+ base.s.size = 0;
+ freep = 0;
+ pool_free_pos = 0;
+}
+
+
+void memmgr_print_stats()
+{
+
+ mem_header_t* p;
+
+ printf("------ Memory manager stats ------\n\n");
+ printf( "Pool: free_pos = %lu (%lu bytes left)\n\n",
+ pool_free_pos,8 * 1024 - pool_free_pos);
+
+ p = (mem_header_t*) pool;
+
+ while (p < (mem_header_t*) (pool + pool_free_pos))
+ {
+ printf( " * Addr: 0x%8lu; Size: %8lu\n",
+ p, p->s.size);
+
+ p += p->s.size;
+ }
+
+ printf("\nFree list:\n\n");
+
+ if (freep)
+ {
+ p = freep;
+
+ while (1)
+ {
+ printf( " * Addr: 0x%8lu; Size: %8lu; Next: 0x%8lu\n",
+ p, p->s.size, p->s.next);
+
+ p = p->s.next;
+
+ if (p == freep)
+ break;
+ }
+ }
+ else
+ {
+ printf("Empty\n");
+ }
+
+ printf("\n");
+
+}
+
+
+static mem_header_t* get_mem_from_pool(ulong nquantas)
+{
+ ulong total_req_size;
+
+ mem_header_t* h;
+
+ if (nquantas < 16)
+ nquantas = 16;
+
+ total_req_size = nquantas * sizeof(mem_header_t);
+
+ if (pool_free_pos + total_req_size <= 8 * 1024)
+ {
+ h = (mem_header_t*) (pool + pool_free_pos);
+ h->s.size = nquantas;
+ memmgr_free((void*) (h + 1));
+ pool_free_pos += total_req_size;
+ }
+ else
+ {
+ return 0;
+ }
+
+ return freep;
+}
+
+
+
+
+
+
+
+
+
+
+void* memmgr_alloc(ulong nbytes)
+{
+ mem_header_t* p;
+ mem_header_t* prevp;
+
+
+
+
+
+ ulong nquantas = (nbytes + sizeof(mem_header_t) - 1) / sizeof(mem_header_t) + 1;
+
+
+
+
+ if ((prevp = freep) == 0)
+ {
+ base.s.next = freep = prevp = &base;
+ base.s.size = 0;
+ }
+
+ for (p = prevp->s.next; ; prevp = p, p = p->s.next)
+ {
+
+ if (p->s.size >= nquantas)
+ {
+
+ if (p->s.size == nquantas)
+ {
+
+
+
+ prevp->s.next = p->s.next;
+ }
+ else
+ {
+ p->s.size -= nquantas;
+ p += p->s.size;
+ p->s.size = nquantas;
+ }
+
+ freep = prevp;
+ return (void*) (p + 1);
+ }
+
+
+
+
+
+
+
+ else if (p == freep)
+ {
+ if ((p = get_mem_from_pool(nquantas)) == 0)
+ {
+
+
+
+ return 0;
+ }
+ }
+ }
+}
+
+
+
+
+
+
+
+void memmgr_free(void* ap)
+{
+ mem_header_t* block;
+ mem_header_t* p;
+
+
+ block = ((mem_header_t*) ap) - 1;
+
+
+
+
+ for (p = freep; !(block > p && block < p->s.next); p = p->s.next)
+ {
+
+
+
+
+
+ if (p >= p->s.next && (block > p || block < p->s.next))
+ break;
+ }
+
+
+
+ if (block + block->s.size == p->s.next)
+ {
+ block->s.size += p->s.next->s.size;
+ block->s.next = p->s.next->s.next;
+ }
+ else
+ {
+ block->s.next = p->s.next;
+ }
+
+
+
+ if (p + p->s.size == block)
+ {
+ p->s.size += block->s.size;
+ p->s.next = block->s.next;
+ }
+ else
+ {
+ p->s.next = block;
+ }
+
+ freep = p;
+}
diff --git a/tests/c_files/simplemain.c b/tests/c_files/simplemain.c new file mode 100644 index 0000000..abbe5d8 --- /dev/null +++ b/tests/c_files/simplemain.c @@ -0,0 +1,5 @@ +#include "hdir\emptydir\..\9\inc.h" + +int main() { + return 0; +} diff --git a/tests/c_files/year.c b/tests/c_files/year.c new file mode 100644 index 0000000..11d9475 --- /dev/null +++ b/tests/c_files/year.c @@ -0,0 +1,60 @@ +#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+/* C99 bools */
+_Bool just_a_flag = false;
+bool another_flag = true;
+
+void convert(int thousands, int hundreds, int tens, int ones)
+{
+char *num[] = {"", "One", "Two", "Three", "Four", "Five", "Six",
+ "Seven", "Eight", "Nine"};
+
+char *for_ten[] = {"", "", "Twenty", "Thirty", "Fourty", "Fifty", "Sixty",
+ "Seventy", "Eighty", "Ninty"};
+
+char *af_ten[] = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen",
+ "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Ninteen"};
+
+ printf("\nThe year in words is:\n");
+
+ printf("%s thousand", num[thousands]);
+ if (hundreds != 0)
+ printf(" %s hundred", num[hundreds]);
+
+ if (tens != 1)
+ printf(" %s %s", for_ten[tens], num[ones]);
+ else
+ printf(" %s", af_ten[ones]);
+
+ va_list jajaja;
+}
+
+
+int main()
+{
+int year;
+int n1000, n100, n10, n1;
+
+ printf("\nEnter the year (4 digits): ");
+ scanf("%d", &year);
+
+ if (year > 9999 || year < 1000)
+ {
+ printf("\nError !! The year must contain 4 digits.");
+ exit(EXIT_FAILURE);
+ }
+
+ n1000 = year/1000;
+ n100 = ((year)%1000)/100;
+ n10 = (year%100)/10;
+ n1 = ((year%10)%10);
+
+ convert(n1000, n100, n10, n1);
+
+return 0;
+}
+
+
diff --git a/tests/test_c_ast.py b/tests/test_c_ast.py new file mode 100644 index 0000000..6ea3ceb --- /dev/null +++ b/tests/test_c_ast.py @@ -0,0 +1,150 @@ +import pprint +import re +import sys +import unittest +import weakref + +sys.path.insert(0, '..') +import pycparser.c_ast as c_ast +import pycparser.plyparser as plyparser + + +class Test_c_ast(unittest.TestCase): + def test_BinaryOp(self): + b1 = c_ast.BinaryOp( + op='+', + left=c_ast.Constant(type='int', value='6'), + right=c_ast.ID(name='joe')) + + self.assertIsInstance(b1.left, c_ast.Constant) + self.assertEqual(b1.left.type, 'int') + self.assertEqual(b1.left.value, '6') + + self.assertIsInstance(b1.right, c_ast.ID) + self.assertEqual(b1.right.name, 'joe') + + def test_weakref_works_on_nodes(self): + c1 = c_ast.Constant(type='float', value='3.14') + wr = weakref.ref(c1) + cref = wr() + self.assertEqual(cref.type, 'float') + self.assertEqual(weakref.getweakrefcount(c1), 1) + + def test_weakref_works_on_coord(self): + coord = plyparser.Coord(file='a', line=2) + wr = weakref.ref(coord) + cref = wr() + self.assertEqual(cref.line, 2) + self.assertEqual(weakref.getweakrefcount(coord), 1) + + +class TestNodeVisitor(unittest.TestCase): + class ConstantVisitor(c_ast.NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + def test_scalar_children(self): + b1 = c_ast.BinaryOp( + op='+', + left=c_ast.Constant(type='int', value='6'), + right=c_ast.ID(name='joe')) + + cv = self.ConstantVisitor() + cv.visit(b1) + + self.assertEqual(cv.values, ['6']) + + b2 = c_ast.BinaryOp( + op='*', + left=c_ast.Constant(type='int', value='111'), + right=b1) + + b3 = c_ast.BinaryOp( + op='^', + left=b2, + right=b1) + + cv = self.ConstantVisitor() + cv.visit(b3) + + self.assertEqual(cv.values, ['111', '6', '6']) + + def tests_list_children(self): + c1 = c_ast.Constant(type='float', value='5.6') + c2 = c_ast.Constant(type='char', value='t') + + b1 = c_ast.BinaryOp( + op='+', + left=c1, + right=c2) + + b2 = c_ast.BinaryOp( + op='-', + left=b1, + right=c2) + + comp = c_ast.Compound( + block_items=[b1, b2, c1, c2]) + + cv = self.ConstantVisitor() + cv.visit(comp) + + self.assertEqual(cv.values, + ['5.6', 't', '5.6', 't', 't', '5.6', 't']) + + def test_repr(self): + c1 = c_ast.Constant(type='float', value='5.6') + c2 = c_ast.Constant(type='char', value='t') + + b1 = c_ast.BinaryOp( + op='+', + left=c1, + right=c2) + + b2 = c_ast.BinaryOp( + op='-', + left=b1, + right=c2) + + comp = c_ast.Compound( + block_items=[b1, b2, c1, c2]) + + expected = ("Compound(block_items=[BinaryOp(op='+',\n" + " left=Constant(type='float',\n" + " value='5.6'\n" + " ),\n" + " right=Constant(type='char',\n" + " value='t'\n" + " )\n" + " ),\n" + " BinaryOp(op='-',\n" + " left=BinaryOp(op='+',\n" + " left=Constant(type='float',\n" + " value='5.6'\n" + " ),\n" + " right=Constant(type='char',\n" + " value='t'\n" + " )\n" + " ),\n" + " right=Constant(type='char',\n" + " value='t'\n" + " )\n" + " ),\n" + " Constant(type='float',\n" + " value='5.6'\n" + " ),\n" + " Constant(type='char',\n" + " value='t'\n" + " )\n" + " ]\n" + " )") + + self.assertEqual(repr(comp), + expected) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_c_generator.py b/tests/test_c_generator.py new file mode 100644 index 0000000..3727f91 --- /dev/null +++ b/tests/test_c_generator.py @@ -0,0 +1,337 @@ +import sys +import textwrap +import unittest + +# Run from the root dir +sys.path.insert(0, '.') + +from pycparser import c_parser, c_generator, c_ast + +_c_parser = c_parser.CParser( + lex_optimize=False, + yacc_debug=True, + yacc_optimize=False, + yacctab='yacctab') + + +def compare_asts(ast1, ast2): + if type(ast1) != type(ast2): + return False + if isinstance(ast1, tuple) and isinstance(ast2, tuple): + if ast1[0] != ast2[0]: + return False + ast1 = ast1[1] + ast2 = ast2[1] + return compare_asts(ast1, ast2) + for attr in ast1.attr_names: + if getattr(ast1, attr) != getattr(ast2, attr): + return False + for i, c1 in enumerate(ast1.children()): + if compare_asts(c1, ast2.children()[i]) == False: + return False + return True + + +def parse_to_ast(src): + return _c_parser.parse(src) + + +class TestFunctionDeclGeneration(unittest.TestCase): + class _FuncDeclVisitor(c_ast.NodeVisitor): + def __init__(self): + self.stubs = [] + + def visit_FuncDecl(self, node): + gen = c_generator.CGenerator() + self.stubs.append(gen.visit(node)) + + def test_partial_funcdecl_generation(self): + src = r''' + void noop(void); + void *something(void *thing); + int add(int x, int y);''' + ast = parse_to_ast(src) + v = TestFunctionDeclGeneration._FuncDeclVisitor() + v.visit(ast) + self.assertEqual(len(v.stubs), 3) + self.assertTrue(r'void noop(void)' in v.stubs) + self.assertTrue(r'void *something(void *thing)' in v.stubs) + self.assertTrue(r'int add(int x, int y)' in v.stubs) + + +class TestCtoC(unittest.TestCase): + def _run_c_to_c(self, src): + ast = parse_to_ast(src) + generator = c_generator.CGenerator() + return generator.visit(ast) + + def _assert_ctoc_correct(self, src): + """ Checks that the c2c translation was correct by parsing the code + generated by c2c for src and comparing the AST with the original + AST. + """ + src2 = self._run_c_to_c(src) + self.assertTrue(compare_asts(parse_to_ast(src), parse_to_ast(src2)), + src2) + + def test_trivial_decls(self): + self._assert_ctoc_correct('int a;') + self._assert_ctoc_correct('int b, a;') + self._assert_ctoc_correct('int c, b, a;') + + def test_complex_decls(self): + self._assert_ctoc_correct('int** (*a)(void);') + self._assert_ctoc_correct('int** (*a)(void*, int);') + self._assert_ctoc_correct('int (*b)(char * restrict k, float);') + self._assert_ctoc_correct('int test(const char* const* arg);') + self._assert_ctoc_correct('int test(const char** const arg);') + + #s = 'int test(const char* const* arg);' + #parse_to_ast(s).show() + + def test_ternary(self): + self._assert_ctoc_correct(''' + int main(void) + { + int a, b; + (a == 0) ? (b = 1) : (b = 2); + }''') + + def test_casts(self): + self._assert_ctoc_correct(r''' + int main() { + int b = (int) f; + int c = (int*) f; + }''') + self._assert_ctoc_correct(r''' + int main() { + int a = (int) b + 8; + int t = (int) c; + } + ''') + + def test_initlist(self): + self._assert_ctoc_correct('int arr[] = {1, 2, 3};') + + def test_exprs(self): + self._assert_ctoc_correct(''' + int main(void) + { + int a; + int b = a++; + int c = ++a; + int d = a--; + int e = --a; + }''') + + def test_statements(self): + # note two minuses here + self._assert_ctoc_correct(r''' + int main() { + int a; + a = 5; + ; + b = - - a; + return a; + }''') + + def test_struct_decl(self): + self._assert_ctoc_correct(r''' + typedef struct node_t { + struct node_t* next; + int data; + } node; + ''') + + def test_krstyle(self): + self._assert_ctoc_correct(r''' + int main(argc, argv) + int argc; + char** argv; + { + return 0; + } + ''') + + def test_switchcase(self): + self._assert_ctoc_correct(r''' + int main() { + switch (myvar) { + case 10: + { + k = 10; + p = k + 1; + break; + } + case 20: + case 30: + return 20; + default: + break; + } + } + ''') + + def test_nest_initializer_list(self): + self._assert_ctoc_correct(r''' + int main() + { + int i[1][1] = { { 1 } }; + }''') + + def test_nest_named_initializer(self): + self._assert_ctoc_correct(r'''struct test + { + int i; + struct test_i_t + { + int k; + } test_i; + int j; + }; + struct test test_var = {.i = 0, .test_i = {.k = 1}, .j = 2}; + ''') + + def test_expr_list_in_initializer_list(self): + self._assert_ctoc_correct(r''' + int main() + { + int i[1] = { (1, 2) }; + }''') + + def test_issue36(self): + self._assert_ctoc_correct(r''' + int main() { + }''') + + def test_issue37(self): + self._assert_ctoc_correct(r''' + int main(void) + { + unsigned size; + size = sizeof(size); + return 0; + }''') + + def test_issue66(self): + # A non-existing body must not be generated + # (previous valid behavior, still working) + self._assert_ctoc_correct(r''' + struct foo; + ''') + # An empty body must be generated + # (added behavior) + self._assert_ctoc_correct(r''' + struct foo {}; + ''') + + def test_issue83(self): + self._assert_ctoc_correct(r''' + void x(void) { + int i = (9, k); + } + ''') + + def test_issue84(self): + self._assert_ctoc_correct(r''' + void x(void) { + for (int i = 0;;) + i; + } + ''') + + def test_issue246(self): + self._assert_ctoc_correct(r''' + int array[3] = {[0] = 0, [1] = 1, [1+1] = 2}; + ''') + + def test_exprlist_with_semi(self): + self._assert_ctoc_correct(r''' + void x() { + if (i < j) + tmp = C[i], C[i] = C[j], C[j] = tmp; + if (i <= j) + i++, j--; + } + ''') + + def test_exprlist_with_subexprlist(self): + self._assert_ctoc_correct(r''' + void x() { + (a = b, (b = c, c = a)); + } + ''') + + def test_comma_operator_funcarg(self): + self._assert_ctoc_correct(r''' + void f(int x) { return x; } + int main(void) { f((1, 2)); return 0; } + ''') + + def test_comma_op_in_ternary(self): + self._assert_ctoc_correct(r''' + void f() { + (0, 0) ? (0, 0) : (0, 0); + } + ''') + + def test_comma_op_assignment(self): + self._assert_ctoc_correct(r''' + void f() { + i = (a, b, c); + } + ''') + + def test_pragma(self): + self._assert_ctoc_correct(r''' + #pragma foo + void f() { + #pragma bar + i = (a, b, c); + } + typedef struct s { + #pragma baz + } s; + ''') + + def test_compound_literal(self): + self._assert_ctoc_correct('char **foo = (char *[]){ "x", "y", "z" };') + self._assert_ctoc_correct('int i = ++(int){ 1 };') + self._assert_ctoc_correct('struct foo_s foo = (struct foo_s){ 1, 2 };') + + def test_enum(self): + self._assert_ctoc_correct(r''' + enum e + { + a, + b = 2, + c = 3 + }; + ''') + self._assert_ctoc_correct(r''' + enum f + { + g = 4, + h, + i + }; + ''') + + def test_enum_typedef(self): + self._assert_ctoc_correct('typedef enum EnumName EnumTypedefName;') + + def test_generate_struct_union_enum_exception(self): + generator = c_generator.CGenerator() + self.assertRaises( + AssertionError, + generator._generate_struct_union_enum, + n=c_ast.Struct( + name='TestStruct', + decls=[], + ), + name='', + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_c_lexer.py b/tests/test_c_lexer.py new file mode 100644 index 0000000..11c7b26 --- /dev/null +++ b/tests/test_c_lexer.py @@ -0,0 +1,447 @@ +import re +import sys +import unittest + +sys.path.insert(0, '..') +from pycparser.c_lexer import CLexer + + +def token_list(clex): + return list(iter(clex.token, None)) + + +def token_types(clex): + return [i.type for i in token_list(clex)] + + +class TestCLexerNoErrors(unittest.TestCase): + """ Test lexing of strings that are not supposed to cause + errors. Therefore, the error_func passed to the lexer + raises an exception. + """ + def error_func(self, msg, line, column): + self.fail(msg) + + def on_lbrace_func(self): + pass + + def on_rbrace_func(self): + pass + + def type_lookup_func(self, typ): + if typ.startswith('mytype'): + return True + else: + return False + + def setUp(self): + self.clex = CLexer(self.error_func, lambda: None, lambda: None, + self.type_lookup_func) + self.clex.build(optimize=False) + + def assertTokensTypes(self, str, types): + self.clex.input(str) + self.assertEqual(token_types(self.clex), types) + + def test_trivial_tokens(self): + self.assertTokensTypes('1', ['INT_CONST_DEC']) + self.assertTokensTypes('-', ['MINUS']) + self.assertTokensTypes('volatile', ['VOLATILE']) + self.assertTokensTypes('...', ['ELLIPSIS']) + self.assertTokensTypes('++', ['PLUSPLUS']) + self.assertTokensTypes('case int', ['CASE', 'INT']) + self.assertTokensTypes('caseint', ['ID']) + self.assertTokensTypes('$dollar cent$', ['ID', 'ID']) + self.assertTokensTypes('i ^= 1;', ['ID', 'XOREQUAL', 'INT_CONST_DEC', 'SEMI']) + + def test_id_typeid(self): + self.assertTokensTypes('myt', ['ID']) + self.assertTokensTypes('mytype', ['TYPEID']) + self.assertTokensTypes('mytype6 var', ['TYPEID', 'ID']) + + def test_integer_constants(self): + self.assertTokensTypes('12', ['INT_CONST_DEC']) + self.assertTokensTypes('12u', ['INT_CONST_DEC']) + self.assertTokensTypes('12l', ['INT_CONST_DEC']) + self.assertTokensTypes('199872Ul', ['INT_CONST_DEC']) + self.assertTokensTypes('199872lU', ['INT_CONST_DEC']) + self.assertTokensTypes('199872LL', ['INT_CONST_DEC']) + self.assertTokensTypes('199872ull', ['INT_CONST_DEC']) + self.assertTokensTypes('199872llu', ['INT_CONST_DEC']) + self.assertTokensTypes('1009843200000uLL', ['INT_CONST_DEC']) + self.assertTokensTypes('1009843200000LLu', ['INT_CONST_DEC']) + + self.assertTokensTypes('077', ['INT_CONST_OCT']) + self.assertTokensTypes('0123456L', ['INT_CONST_OCT']) + + self.assertTokensTypes('0xf7', ['INT_CONST_HEX']) + self.assertTokensTypes('0b110', ['INT_CONST_BIN']) + self.assertTokensTypes('0x01202AAbbf7Ul', ['INT_CONST_HEX']) + + # no 0 before x, so ID catches it + self.assertTokensTypes('xf7', ['ID']) + + # - is MINUS, the rest a constnant + self.assertTokensTypes('-1', ['MINUS', 'INT_CONST_DEC']) + + def test_special_names(self): + self.assertTokensTypes('sizeof offsetof', ['SIZEOF', 'OFFSETOF']) + + def test_floating_constants(self): + self.assertTokensTypes('1.5f', ['FLOAT_CONST']) + self.assertTokensTypes('01.5', ['FLOAT_CONST']) + self.assertTokensTypes('.15L', ['FLOAT_CONST']) + self.assertTokensTypes('0.', ['FLOAT_CONST']) + + # but just a period is a period + self.assertTokensTypes('.', ['PERIOD']) + + self.assertTokensTypes('3.3e-3', ['FLOAT_CONST']) + self.assertTokensTypes('.7e25L', ['FLOAT_CONST']) + self.assertTokensTypes('6.e+125f', ['FLOAT_CONST']) + self.assertTokensTypes('666e666', ['FLOAT_CONST']) + self.assertTokensTypes('00666e+3', ['FLOAT_CONST']) + + # but this is a hex integer + 3 + self.assertTokensTypes('0x0666e+3', ['INT_CONST_HEX', 'PLUS', 'INT_CONST_DEC']) + + def test_hexadecimal_floating_constants(self): + self.assertTokensTypes('0xDE.488641p0', ['HEX_FLOAT_CONST']) + self.assertTokensTypes('0x.488641p0', ['HEX_FLOAT_CONST']) + self.assertTokensTypes('0X12.P0', ['HEX_FLOAT_CONST']) + + def test_char_constants(self): + self.assertTokensTypes(r"""'x'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""L'x'""", ['WCHAR_CONST']) + self.assertTokensTypes(r"""'\t'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""'\''""", ['CHAR_CONST']) + self.assertTokensTypes(r"""'\?'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""'\012'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""'\x2f'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""'\x2f12'""", ['CHAR_CONST']) + self.assertTokensTypes(r"""L'\xaf'""", ['WCHAR_CONST']) + + def test_on_rbrace_lbrace(self): + braces = [] + def on_lbrace(): + braces.append('{') + def on_rbrace(): + braces.append('}') + clex = CLexer(self.error_func, on_lbrace, on_rbrace, + self.type_lookup_func) + clex.build(optimize=False) + clex.input('hello { there } } and again }}{') + token_list(clex) + self.assertEqual(braces, ['{', '}', '}', '}', '}', '{']) + + def test_string_literal(self): + self.assertTokensTypes('"a string"', ['STRING_LITERAL']) + self.assertTokensTypes('L"ing"', ['WSTRING_LITERAL']) + self.assertTokensTypes( + '"i am a string too \t"', + ['STRING_LITERAL']) + self.assertTokensTypes( + r'''"esc\ape \"\'\? \0234 chars \rule"''', + ['STRING_LITERAL']) + self.assertTokensTypes( + r'''"hello 'joe' wanna give it a \"go\"?"''', + ['STRING_LITERAL']) + self.assertTokensTypes( + '"\123\123\123\123\123\123\123\123\123\123\123\123\123\123\123\123"', + ['STRING_LITERAL']) + + def test_mess(self): + self.assertTokensTypes( + r'[{}]()', + ['LBRACKET', + 'LBRACE', 'RBRACE', + 'RBRACKET', + 'LPAREN', 'RPAREN']) + + self.assertTokensTypes( + r'()||!C&~Z?J', + ['LPAREN', 'RPAREN', + 'LOR', + 'LNOT', 'ID', + 'AND', + 'NOT', 'ID', + 'CONDOP', 'ID']) + + self.assertTokensTypes( + r'+-*/%|||&&&^><>=<===!=', + ['PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MOD', + 'LOR', 'OR', + 'LAND', 'AND', + 'XOR', + 'GT', 'LT', 'GE', 'LE', 'EQ', 'NE']) + + self.assertTokensTypes( + r'++--->?.,;:', + ['PLUSPLUS', 'MINUSMINUS', + 'ARROW', 'CONDOP', + 'PERIOD', 'COMMA', 'SEMI', 'COLON']) + + def test_exprs(self): + self.assertTokensTypes( + 'bb-cc', + ['ID', 'MINUS', 'ID']) + + self.assertTokensTypes( + 'foo & 0xFF', + ['ID', 'AND', 'INT_CONST_HEX']) + + self.assertTokensTypes( + '(2+k) * 62', + ['LPAREN', 'INT_CONST_DEC', 'PLUS', 'ID', + 'RPAREN', 'TIMES', 'INT_CONST_DEC'],) + + self.assertTokensTypes( + 'x | y >> z', + ['ID', 'OR', 'ID', 'RSHIFT', 'ID']) + + self.assertTokensTypes( + 'x <<= z << 5', + ['ID', 'LSHIFTEQUAL', 'ID', 'LSHIFT', 'INT_CONST_DEC']) + + self.assertTokensTypes( + 'x = y > 0 ? y : -6', + ['ID', 'EQUALS', + 'ID', 'GT', 'INT_CONST_OCT', + 'CONDOP', + 'ID', + 'COLON', + 'MINUS', 'INT_CONST_DEC']) + + self.assertTokensTypes( + 'a+++b', + ['ID', 'PLUSPLUS', 'PLUS', 'ID']) + + def test_statements(self): + self.assertTokensTypes( + 'for (int i = 0; i < n; ++i)', + ['FOR', 'LPAREN', + 'INT', 'ID', 'EQUALS', 'INT_CONST_OCT', 'SEMI', + 'ID', 'LT', 'ID', 'SEMI', + 'PLUSPLUS', 'ID', + 'RPAREN']) + + self.assertTokensTypes( + 'self: goto self;', + ['ID', 'COLON', 'GOTO', 'ID', 'SEMI']) + + self.assertTokensTypes( + """ switch (typ) + { + case TYPE_ID: + m = 5; + break; + default: + m = 8; + }""", + ['SWITCH', 'LPAREN', 'ID', 'RPAREN', + 'LBRACE', + 'CASE', 'ID', 'COLON', + 'ID', 'EQUALS', 'INT_CONST_DEC', 'SEMI', + 'BREAK', 'SEMI', + 'DEFAULT', 'COLON', + 'ID', 'EQUALS', 'INT_CONST_DEC', 'SEMI', + 'RBRACE']) + + def test_preprocessor_line(self): + self.assertTokensTypes('#abracadabra', ['PPHASH', 'ID']) + + str = r""" + 546 + #line 66 "kwas\df.h" + id 4 + dsf + # 9 + armo + #line 10 "..\~..\test.h" + tok1 + #line 99999 "include/me.h" + tok2 + """ + + #~ self.clex.filename + self.clex.input(str) + self.clex.reset_lineno() + + t1 = self.clex.token() + self.assertEqual(t1.type, 'INT_CONST_DEC') + self.assertEqual(t1.lineno, 2) + + t2 = self.clex.token() + self.assertEqual(t2.type, 'ID') + self.assertEqual(t2.value, 'id') + self.assertEqual(t2.lineno, 66) + self.assertEqual(self.clex.filename, r'kwas\df.h') + + for i in range(3): + t = self.clex.token() + + self.assertEqual(t.type, 'ID') + self.assertEqual(t.value, 'armo') + self.assertEqual(t.lineno, 9) + self.assertEqual(self.clex.filename, r'kwas\df.h') + + t4 = self.clex.token() + self.assertEqual(t4.type, 'ID') + self.assertEqual(t4.value, 'tok1') + self.assertEqual(t4.lineno, 10) + self.assertEqual(self.clex.filename, r'..\~..\test.h') + + t5 = self.clex.token() + self.assertEqual(t5.type, 'ID') + self.assertEqual(t5.value, 'tok2') + self.assertEqual(t5.lineno, 99999) + self.assertEqual(self.clex.filename, r'include/me.h') + + def test_preprocessor_line_funny(self): + str = r''' + #line 10 "..\6\joe.h" + 10 + ''' + self.clex.input(str) + self.clex.reset_lineno() + + t1 = self.clex.token() + self.assertEqual(t1.type, 'INT_CONST_DEC') + self.assertEqual(t1.lineno, 10) + self.assertEqual(self.clex.filename, r'..\6\joe.h') + + + def test_preprocessor_pragma(self): + str = ''' + 42 + #pragma + #pragma helo me + #pragma once + # pragma omp parallel private(th_id) + #\tpragma {pack: 2, smack: 3} + #pragma <includeme.h> "nowit.h" + #pragma "string" + #pragma somestring="some_other_string" + #pragma id 124124 and numbers 0235495 + 59 + ''' + # Check that pragmas are tokenized, including trailing string + self.clex.input(str) + self.clex.reset_lineno() + + t1 = self.clex.token() + self.assertEqual(t1.type, 'INT_CONST_DEC') + + t2 = self.clex.token() + self.assertEqual(t2.type, 'PPPRAGMA') + + t3 = self.clex.token() + self.assertEqual(t3.type, 'PPPRAGMA') + + t4 = self.clex.token() + self.assertEqual(t4.type, 'PPPRAGMASTR') + self.assertEqual(t4.value, 'helo me') + + for i in range(3): + t = self.clex.token() + + t5 = self.clex.token() + self.assertEqual(t5.type, 'PPPRAGMASTR') + self.assertEqual(t5.value, 'omp parallel private(th_id)') + + for i in range(5): + ta = self.clex.token() + self.assertEqual(ta.type, 'PPPRAGMA') + tb = self.clex.token() + self.assertEqual(tb.type, 'PPPRAGMASTR') + + t6 = self.clex.token() + self.assertEqual(t6.type, 'INT_CONST_DEC') + self.assertEqual(t6.lineno, 12) + + + +# Keeps all the errors the lexer spits in one place, to allow +# easier modification if the error syntax changes. +# +ERR_ILLEGAL_CHAR = 'Illegal character' +ERR_OCTAL = 'Invalid octal constant' +ERR_UNMATCHED_QUOTE = 'Unmatched \'' +ERR_INVALID_CCONST = 'Invalid char constant' +ERR_STRING_ESCAPE = 'String contains invalid escape' + +ERR_FILENAME_BEFORE_LINE = 'filename before line' +ERR_LINENUM_MISSING = 'line number missing' +ERR_INVALID_LINE_DIRECTIVE = 'invalid #line directive' + + +class TestCLexerErrors(unittest.TestCase): + """ Test lexing of erroneous strings. + Works by passing an error functions that saves the error + in an attribute for later perusal. + """ + def error_func(self, msg, line, column): + self.error = msg + + def on_lbrace_func(self): + pass + + def on_rbrace_func(self): + pass + + def type_lookup_func(self, typ): + return False + + def setUp(self): + self.clex = CLexer(self.error_func, self.on_lbrace_func, + self.on_rbrace_func, self.type_lookup_func) + self.clex.build(optimize=False) + self.error = "" + + def assertLexerError(self, str, error_like): + # feed the string to the lexer + self.clex.input(str) + + # Pulls all tokens from the string. Errors will + # be written into self.error by the error_func + # callback + # + token_types(self.clex) + + # compare the error to the expected + self.assertTrue(re.search(error_like, self.error), + "\nExpected error matching: %s\nGot: %s" % + (error_like, self.error)) + + # clear last error, for the sake of subsequent invocations + self.error = "" + + def test_trivial_tokens(self): + self.assertLexerError('@', ERR_ILLEGAL_CHAR) + self.assertLexerError('`', ERR_ILLEGAL_CHAR) + self.assertLexerError('\\', ERR_ILLEGAL_CHAR) + + def test_integer_constants(self): + self.assertLexerError('029', ERR_OCTAL) + self.assertLexerError('012345678', ERR_OCTAL) + + def test_char_constants(self): + self.assertLexerError("'", ERR_UNMATCHED_QUOTE) + self.assertLexerError("'b\n", ERR_UNMATCHED_QUOTE) + + self.assertLexerError("'jx'", ERR_INVALID_CCONST) + self.assertLexerError(r"'\*'", ERR_INVALID_CCONST) + + def test_string_literals(self): + self.assertLexerError(r'"jx\9"', ERR_STRING_ESCAPE) + self.assertLexerError(r'"hekllo\* on ix"', ERR_STRING_ESCAPE) + self.assertLexerError(r'L"hekllo\* on ix"', ERR_STRING_ESCAPE) + + def test_preprocessor(self): + self.assertLexerError('#line "ka"', ERR_FILENAME_BEFORE_LINE) + self.assertLexerError('#line df', ERR_INVALID_LINE_DIRECTIVE) + self.assertLexerError('#line \n', ERR_LINENUM_MISSING) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_c_parser.py b/tests/test_c_parser.py new file mode 100755 index 0000000..a48f1c6 --- /dev/null +++ b/tests/test_c_parser.py @@ -0,0 +1,2132 @@ +#!/usr/bin/env python + +import pprint +import re +import os, sys +import io +import unittest + +sys.path[0:0] = ['.', '..'] + +from pycparser import c_parser +from pycparser.c_ast import * +from pycparser.c_parser import CParser, Coord, ParseError + +_c_parser = c_parser.CParser( + lex_optimize=False, + yacc_debug=True, + yacc_optimize=False, + yacctab='yacctab') + + +def expand_decl(decl): + """ Converts the declaration into a nested list. + """ + typ = type(decl) + + if typ == TypeDecl: + return ['TypeDecl', expand_decl(decl.type)] + elif typ == IdentifierType: + return ['IdentifierType', decl.names] + elif typ == ID: + return ['ID', decl.name] + elif typ in [Struct, Union]: + decls = [expand_decl(d) for d in decl.decls or []] + return [typ.__name__, decl.name, decls] + else: + nested = expand_decl(decl.type) + + if typ == Decl: + if decl.quals: + return ['Decl', decl.quals, decl.name, nested] + else: + return ['Decl', decl.name, nested] + elif typ == Typename: # for function parameters + if decl.quals: + return ['Typename', decl.quals, nested] + else: + return ['Typename', nested] + elif typ == ArrayDecl: + dimval = decl.dim.value if decl.dim else '' + return ['ArrayDecl', dimval, decl.dim_quals, nested] + elif typ == PtrDecl: + if decl.quals: + return ['PtrDecl', decl.quals, nested] + else: + return ['PtrDecl', nested] + elif typ == Typedef: + return ['Typedef', decl.name, nested] + elif typ == FuncDecl: + if decl.args: + params = [expand_decl(param) for param in decl.args.params] + else: + params = [] + return ['FuncDecl', params, nested] + + +def expand_init(init): + """ Converts an initialization into a nested list + """ + typ = type(init) + + if typ == NamedInitializer: + des = [expand_init(dp) for dp in init.name] + return (des, expand_init(init.expr)) + elif typ in (InitList, ExprList): + return [expand_init(expr) for expr in init.exprs] + elif typ == Constant: + return ['Constant', init.type, init.value] + elif typ == ID: + return ['ID', init.name] + elif typ == UnaryOp: + return ['UnaryOp', init.op, expand_decl(init.expr)] + + +class TestCParser_base(unittest.TestCase): + def parse(self, txt, filename=''): + return self.cparser.parse(txt, filename) + + def setUp(self): + self.cparser = _c_parser + + def assert_coord(self, node, line, column=None, file=None): + self.assertEqual(node.coord.line, line) + if column is not None: + self.assertEqual(node.coord.column, column) + if file: + self.assertEqual(node.coord.file, file) + + + +class TestCParser_fundamentals(TestCParser_base): + def get_decl(self, txt, index=0): + """ Given a source and an index returns the expanded + declaration at that index. + + FileAST holds a list of 'external declarations'. + index is the offset of the desired declaration in that + list. + """ + t = self.parse(txt).ext[index] + return expand_decl(t) + + def get_decl_init(self, txt, index=0): + """ Returns the expanded initializer of the declaration + at index. + """ + t = self.parse(txt).ext[index] + return expand_init(t.init) + + def test_FileAST(self): + t = self.parse('int a; char c;') + self.assertIsInstance(t, FileAST) + self.assertEqual(len(t.ext), 2) + + # empty file + t2 = self.parse('') + self.assertIsInstance(t2, FileAST) + self.assertEqual(len(t2.ext), 0) + + def test_empty_toplevel_decl(self): + code = 'int foo;;' + t = self.parse(code) + self.assertIsInstance(t, FileAST) + self.assertEqual(len(t.ext), 1) + self.assertEqual(self.get_decl(code), + ['Decl', 'foo', + ['TypeDecl', ['IdentifierType', ['int']]]]) + + def test_coords(self): + """ Tests the "coordinates" of parsed elements - file + name, line and column numbers, with modification + insterted by #line directives. + """ + self.assert_coord(self.parse('int a;').ext[0], 1, 5) + + t1 = """ + int a; + int b;\n\n + int c; + """ + f1 = self.parse(t1, filename='test.c') + self.assert_coord(f1.ext[0], 2, 13, 'test.c') + self.assert_coord(f1.ext[1], 3, 13, 'test.c') + self.assert_coord(f1.ext[2], 6, 13, 'test.c') + + t1_1 = ''' + int main() { + k = p; + printf("%d", b); + return 0; + }''' + f1_1 = self.parse(t1_1, filename='test.c') + self.assert_coord(f1_1.ext[0].body.block_items[0], 3, 13, 'test.c') + self.assert_coord(f1_1.ext[0].body.block_items[1], 4, 13, 'test.c') + + t1_2 = ''' + int main () { + int p = (int) k; + }''' + f1_2 = self.parse(t1_2, filename='test.c') + # make sure that the Cast has a coord (issue 23) + self.assert_coord(f1_2.ext[0].body.block_items[0].init, 3, 21, file='test.c') + + t2 = """ + #line 99 + int c; + """ + self.assert_coord(self.parse(t2).ext[0], 99, 13) + + t3 = """ + int dsf; + char p; + #line 3000 "in.h" + char d; + """ + f3 = self.parse(t3, filename='test.c') + self.assert_coord(f3.ext[0], 2, 13, 'test.c') + self.assert_coord(f3.ext[1], 3, 14, 'test.c') + self.assert_coord(f3.ext[2], 3000, 14, 'in.h') + + t4 = """ + #line 20 "restore.h" + int maydler(char); + + #line 30 "includes/daween.ph" + long j, k; + + #line 50000 + char* ro; + """ + f4 = self.parse(t4, filename='myb.c') + self.assert_coord(f4.ext[0], 20, 13, 'restore.h') + self.assert_coord(f4.ext[1], 30, 14, 'includes/daween.ph') + self.assert_coord(f4.ext[2], 30, 17, 'includes/daween.ph') + self.assert_coord(f4.ext[3], 50000, 13, 'includes/daween.ph') + + t5 = """ + int + #line 99 + c; + """ + self.assert_coord(self.parse(t5).ext[0], 99, 9) + + # coord for ellipsis + t6 = """ + int foo(int j, + ...) { + }""" + f6 = self.parse(t6, filename='z.c') + self.assert_coord(self.parse(t6).ext[0].decl.type.args.params[1], 3, 17) + + def test_forloop_coord(self): + t = '''\ + void foo() { + for(int z=0; z<4; + z++){} + } + ''' + s = self.parse(t, filename='f.c') + forloop = s.ext[0].body.block_items[0] + self.assert_coord(forloop.init, 2, 13, 'f.c') + self.assert_coord(forloop.cond, 2, 26, 'f.c') + self.assert_coord(forloop.next, 3, 17, 'f.c') + + def test_simple_decls(self): + self.assertEqual(self.get_decl('int a;'), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['int']]]]) + + self.assertEqual(self.get_decl('unsigned int a;'), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['unsigned', 'int']]]]) + + self.assertEqual(self.get_decl('_Bool a;'), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['_Bool']]]]) + + self.assertEqual(self.get_decl('float _Complex fcc;'), + ['Decl', 'fcc', ['TypeDecl', ['IdentifierType', ['float', '_Complex']]]]) + + self.assertEqual(self.get_decl('char* string;'), + ['Decl', 'string', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]]) + + self.assertEqual(self.get_decl('long ar[15];'), + ['Decl', 'ar', + ['ArrayDecl', '15', [], + ['TypeDecl', ['IdentifierType', ['long']]]]]) + + self.assertEqual(self.get_decl('long long ar[15];'), + ['Decl', 'ar', + ['ArrayDecl', '15', [], + ['TypeDecl', ['IdentifierType', ['long', 'long']]]]]) + + self.assertEqual(self.get_decl('unsigned ar[];'), + ['Decl', 'ar', + ['ArrayDecl', '', [], + ['TypeDecl', ['IdentifierType', ['unsigned']]]]]) + + self.assertEqual(self.get_decl('int strlen(char* s);'), + ['Decl', 'strlen', + ['FuncDecl', + [['Decl', 's', + ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + self.assertEqual(self.get_decl('int strcmp(char* s1, char* s2);'), + ['Decl', 'strcmp', + ['FuncDecl', + [ ['Decl', 's1', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]], + ['Decl', 's2', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]] + ], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + # function return values and parameters may not have type information + self.assertEqual(self.get_decl('extern foobar(foo, bar);'), + ['Decl', 'foobar', + ['FuncDecl', + [ ['ID', 'foo'], + ['ID', 'bar'] + ], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + def test_int128(self): + self.assertEqual(self.get_decl('__int128 a;'), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['__int128']]]]) + + + def test_nested_decls(self): # the fun begins + self.assertEqual(self.get_decl('char** ar2D;'), + ['Decl', 'ar2D', + ['PtrDecl', ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]]) + + self.assertEqual(self.get_decl('int (*a)[1][2];'), + ['Decl', 'a', + ['PtrDecl', + ['ArrayDecl', '1', [], + ['ArrayDecl', '2', [], + ['TypeDecl', ['IdentifierType', ['int']]]]]]]) + + self.assertEqual(self.get_decl('int *a[1][2];'), + ['Decl', 'a', + ['ArrayDecl', '1', [], + ['ArrayDecl', '2', [], + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['int']]]]]]]) + + self.assertEqual(self.get_decl('char* const* p;'), + ['Decl', 'p', + ['PtrDecl', ['PtrDecl', ['const'], + ['TypeDecl', ['IdentifierType', ['char']]]]]]) + + self.assertEqual(self.get_decl('char* * const p;'), + ['Decl', 'p', + ['PtrDecl', ['const'], ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]]) + + self.assertEqual(self.get_decl('char ***ar3D[40];'), + ['Decl', 'ar3D', + ['ArrayDecl', '40', [], + ['PtrDecl', ['PtrDecl', ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]]]]) + + self.assertEqual(self.get_decl('char (***ar3D)[40];'), + ['Decl', 'ar3D', + ['PtrDecl', ['PtrDecl', ['PtrDecl', + ['ArrayDecl', '40', [], ['TypeDecl', ['IdentifierType', ['char']]]]]]]]) + + self.assertEqual(self.get_decl('int (*x[4])(char, int);'), + ['Decl', 'x', + ['ArrayDecl', '4', [], + ['PtrDecl', + ['FuncDecl', + [ ['Typename', ['TypeDecl', ['IdentifierType', ['char']]]], + ['Typename', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]]) + + self.assertEqual(self.get_decl('char *(*(**foo [][8])())[];'), + ['Decl', 'foo', + ['ArrayDecl', '', [], + ['ArrayDecl', '8', [], + ['PtrDecl', ['PtrDecl', + ['FuncDecl', + [], + ['PtrDecl', + ['ArrayDecl', '', [], + ['PtrDecl', + ['TypeDecl', + ['IdentifierType', ['char']]]]]]]]]]]]) + + # explore named and unnamed function pointer parameters, + # with and without qualifiers + + # unnamed w/o quals + self.assertEqual(self.get_decl('int (*k)(int);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Typename', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + # unnamed w/ quals + self.assertEqual(self.get_decl('int (*k)(const int);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Typename', ['const'], ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + # named w/o quals + self.assertEqual(self.get_decl('int (*k)(int q);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Decl', 'q', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + # named w/ quals + self.assertEqual(self.get_decl('int (*k)(const volatile int q);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Decl', ['const', 'volatile'], 'q', + ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + self.assertEqual(self.get_decl('int (*k)(const volatile int* q);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Decl', ['const', 'volatile'], 'q', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['int']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + # restrict qualifier + self.assertEqual(self.get_decl('int (*k)(restrict int* q);'), + ['Decl', 'k', + ['PtrDecl', + ['FuncDecl', + [['Decl', ['restrict'], 'q', + ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['int']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + def test_func_decls_with_array_dim_qualifiers(self): + self.assertEqual(self.get_decl('int zz(int p[static 10]);'), + ['Decl', 'zz', + ['FuncDecl', + [['Decl', 'p', ['ArrayDecl', '10', ['static'], + ['TypeDecl', ['IdentifierType', ['int']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + self.assertEqual(self.get_decl('int zz(int p[const 10]);'), + ['Decl', 'zz', + ['FuncDecl', + [['Decl', 'p', ['ArrayDecl', '10', ['const'], + ['TypeDecl', ['IdentifierType', ['int']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + self.assertEqual(self.get_decl('int zz(int p[restrict][5]);'), + ['Decl', 'zz', + ['FuncDecl', + [['Decl', 'p', ['ArrayDecl', '', ['restrict'], + ['ArrayDecl', '5', [], + ['TypeDecl', ['IdentifierType', ['int']]]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + self.assertEqual(self.get_decl('int zz(int p[const restrict static 10][5]);'), + ['Decl', 'zz', + ['FuncDecl', + [['Decl', 'p', ['ArrayDecl', '10', ['const', 'restrict', 'static'], + ['ArrayDecl', '5', [], + ['TypeDecl', ['IdentifierType', ['int']]]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + def test_qualifiers_storage_specifiers(self): + def assert_qs(txt, index, quals, storage): + d = self.parse(txt).ext[index] + self.assertEqual(d.quals, quals) + self.assertEqual(d.storage, storage) + + assert_qs("extern int p;", 0, [], ['extern']) + assert_qs("const long p = 6;", 0, ['const'], []) + + d1 = "static const int p, q, r;" + for i in range(3): + assert_qs(d1, i, ['const'], ['static']) + + d2 = "static char * const p;" + assert_qs(d2, 0, [], ['static']) + pdecl = self.parse(d2).ext[0].type + self.assertIsInstance(pdecl, PtrDecl) + self.assertEqual(pdecl.quals, ['const']) + + def test_sizeof(self): + e = """ + void foo() + { + int a = sizeof k; + int b = sizeof(int); + int c = sizeof(int**);; + + char* p = "just to make sure this parses w/o error..."; + int d = sizeof(int()); + } + """ + compound = self.parse(e).ext[0].body + + s1 = compound.block_items[0].init + self.assertIsInstance(s1, UnaryOp) + self.assertEqual(s1.op, 'sizeof') + self.assertIsInstance(s1.expr, ID) + self.assertEqual(s1.expr.name, 'k') + + s2 = compound.block_items[1].init + self.assertEqual(expand_decl(s2.expr), + ['Typename', ['TypeDecl', ['IdentifierType', ['int']]]]) + + s3 = compound.block_items[2].init + self.assertEqual(expand_decl(s3.expr), + ['Typename', + ['PtrDecl', + ['PtrDecl', + ['TypeDecl', + ['IdentifierType', ['int']]]]]]) + + def test_offsetof(self): + e = """ + void foo() { + int a = offsetof(struct S, p); + a.b = offsetof(struct sockaddr, sp) + strlen(bar); + int a = offsetof(struct S, p.q.r); + int a = offsetof(struct S, p[5].q[4][5]); + } + """ + compound = self.parse(e).ext[0].body + s1 = compound.block_items[0].init + self.assertIsInstance(s1, FuncCall) + self.assertIsInstance(s1.name, ID) + self.assertEqual(s1.name.name, 'offsetof') + self.assertIsInstance(s1.args.exprs[0], Typename) + self.assertIsInstance(s1.args.exprs[1], ID) + s3 = compound.block_items[2].init + self.assertIsInstance(s3.args.exprs[1], StructRef) + s4 = compound.block_items[3].init + self.assertIsInstance(s4.args.exprs[1], ArrayRef) + + def test_compound_statement(self): + e = """ + void foo() { + } + """ + compound = self.parse(e).ext[0].body + self.assertIsInstance(compound, Compound) + self.assert_coord(compound, 2) + + # The C99 compound literal feature + # + def test_compound_literals(self): + ps1 = self.parse(r''' + void foo() { + p = (long long){k}; + tc = (struct jk){.a = {1, 2}, .b[0] = t}; + }''') + + compound = ps1.ext[0].body.block_items[0].rvalue + self.assertEqual(expand_decl(compound.type), + ['Typename', ['TypeDecl', ['IdentifierType', ['long', 'long']]]]) + self.assertEqual(expand_init(compound.init), + [['ID', 'k']]) + + compound = ps1.ext[0].body.block_items[1].rvalue + self.assertEqual(expand_decl(compound.type), + ['Typename', ['TypeDecl', ['Struct', 'jk', []]]]) + self.assertEqual(expand_init(compound.init), + [ + ([['ID', 'a']], [['Constant', 'int', '1'], ['Constant', 'int', '2']]), + ([['ID', 'b'], ['Constant', 'int', '0']], ['ID', 't'])]) + + def test_enums(self): + e1 = "enum mycolor op;" + e1_type = self.parse(e1).ext[0].type.type + + self.assertIsInstance(e1_type, Enum) + self.assertEqual(e1_type.name, 'mycolor') + self.assertEqual(e1_type.values, None) + + e2 = "enum mysize {large=20, small, medium} shoes;" + e2_type = self.parse(e2).ext[0].type.type + + self.assertIsInstance(e2_type, Enum) + self.assertEqual(e2_type.name, 'mysize') + + e2_elist = e2_type.values + self.assertIsInstance(e2_elist, EnumeratorList) + + for e2_eval in e2_elist.enumerators: + self.assertIsInstance(e2_eval, Enumerator) + + self.assertEqual(e2_elist.enumerators[0].name, 'large') + self.assertEqual(e2_elist.enumerators[0].value.value, '20') + self.assertEqual(e2_elist.enumerators[2].name, 'medium') + self.assertEqual(e2_elist.enumerators[2].value, None) + + # enum with trailing comma (C99 feature) + e3 = """ + enum + { + red, + blue, + green, + } color; + """ + + e3_type = self.parse(e3).ext[0].type.type + self.assertIsInstance(e3_type, Enum) + e3_elist = e3_type.values + self.assertIsInstance(e3_elist, EnumeratorList) + + for e3_eval in e3_elist.enumerators: + self.assertIsInstance(e3_eval, Enumerator) + + self.assertEqual(e3_elist.enumerators[0].name, 'red') + self.assertEqual(e3_elist.enumerators[0].value, None) + self.assertEqual(e3_elist.enumerators[1].name, 'blue') + self.assertEqual(e3_elist.enumerators[2].name, 'green') + + def test_typedef(self): + # without typedef, error + s1 = """ + node k; + """ + self.assertRaises(ParseError, self.parse, s1) + + # now with typedef, works + s2 = """ + typedef void* node; + node k; + """ + ps2 = self.parse(s2) + self.assertEqual(expand_decl(ps2.ext[0]), + ['Typedef', 'node', + ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['void']]]]]) + + self.assertEqual(expand_decl(ps2.ext[1]), + ['Decl', 'k', + ['TypeDecl', ['IdentifierType', ['node']]]]) + + s3 = """ + typedef int T; + typedef T *pT; + + pT aa, bb; + """ + ps3 = self.parse(s3) + self.assertEqual(expand_decl(ps3.ext[3]), + ['Decl', 'bb', + ['TypeDecl', ['IdentifierType', ['pT']]]]) + + s4 = ''' + typedef char* __builtin_va_list; + typedef __builtin_va_list __gnuc_va_list; + ''' + ps4 = self.parse(s4) + self.assertEqual(expand_decl(ps4.ext[1]), + ['Typedef', '__gnuc_va_list', + ['TypeDecl', + ['IdentifierType', ['__builtin_va_list']]]]) + + s5 = '''typedef struct tagHash Hash;''' + ps5 = self.parse(s5) + self.assertEqual(expand_decl(ps5.ext[0]), + ['Typedef', 'Hash', ['TypeDecl', ['Struct', 'tagHash', []]]]) + + def test_struct_union(self): + s1 = """ + struct { + int id; + char* name; + } joe; + """ + + self.assertEqual(expand_decl(self.parse(s1).ext[0]), + ['Decl', 'joe', + ['TypeDecl', ['Struct', None, + [ ['Decl', 'id', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', 'name', + ['PtrDecl', + ['TypeDecl', + ['IdentifierType', ['char']]]]]]]]]) + + s2 = """ + struct node p; + """ + self.assertEqual(expand_decl(self.parse(s2).ext[0]), + ['Decl', 'p', + ['TypeDecl', ['Struct', 'node', []]]]) + + s21 = """ + union pri ra; + """ + self.assertEqual(expand_decl(self.parse(s21).ext[0]), + ['Decl', 'ra', + ['TypeDecl', ['Union', 'pri', []]]]) + + s3 = """ + struct node* p; + """ + self.assertEqual(expand_decl(self.parse(s3).ext[0]), + ['Decl', 'p', + ['PtrDecl', + ['TypeDecl', ['Struct', 'node', []]]]]) + + s4 = """ + struct node; + """ + self.assertEqual(expand_decl(self.parse(s4).ext[0]), + ['Decl', None, + ['Struct', 'node', []]]) + + s5 = """ + union + { + struct + { + int type; + } n; + + struct + { + int type; + int intnode; + } ni; + } u; + """ + self.assertEqual(expand_decl(self.parse(s5).ext[0]), + ['Decl', 'u', + ['TypeDecl', + ['Union', None, + [['Decl', 'n', + ['TypeDecl', + ['Struct', None, + [['Decl', 'type', + ['TypeDecl', ['IdentifierType', ['int']]]]]]]], + ['Decl', 'ni', + ['TypeDecl', + ['Struct', None, + [['Decl', 'type', + ['TypeDecl', ['IdentifierType', ['int']]]], + ['Decl', 'intnode', + ['TypeDecl', ['IdentifierType', ['int']]]]]]]]]]]]) + + s6 = """ + typedef struct foo_tag + { + void* data; + } foo, *pfoo; + """ + s6_ast = self.parse(s6) + + self.assertEqual(expand_decl(s6_ast.ext[0]), + ['Typedef', 'foo', + ['TypeDecl', + ['Struct', 'foo_tag', + [['Decl', 'data', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['void']]]]]]]]]) + + self.assertEqual(expand_decl(s6_ast.ext[1]), + ['Typedef', 'pfoo', + ['PtrDecl', + ['TypeDecl', + ['Struct', 'foo_tag', + [['Decl', 'data', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['void']]]]]]]]]]) + + s7 = r""" + struct _on_exit_args { + void * _fnargs[32]; + void * _dso_handle[32]; + + long _fntypes; + #line 77 "D:\eli\cpp_stuff\libc_include/sys/reent.h" + + long _is_cxa; + }; + """ + + s7_ast = self.parse(s7, filename='test.c') + self.assert_coord(s7_ast.ext[0].type.decls[2], 6, 22, 'test.c') + self.assert_coord(s7_ast.ext[0].type.decls[3], 78, 22, + r'D:\eli\cpp_stuff\libc_include/sys/reent.h') + + s8 = """ + typedef enum tagReturnCode {SUCCESS, FAIL} ReturnCode; + + typedef struct tagEntry + { + char* key; + char* value; + } Entry; + + + typedef struct tagNode + { + Entry* entry; + + struct tagNode* next; + } Node; + + typedef struct tagHash + { + unsigned int table_size; + + Node** heads; + + } Hash; + """ + s8_ast = self.parse(s8) + self.assertEqual(expand_decl(s8_ast.ext[3]), + ['Typedef', 'Hash', + ['TypeDecl', ['Struct', 'tagHash', + [['Decl', 'table_size', + ['TypeDecl', ['IdentifierType', ['unsigned', 'int']]]], + ['Decl', 'heads', + ['PtrDecl', ['PtrDecl', ['TypeDecl', ['IdentifierType', ['Node']]]]]]]]]]) + + def test_struct_with_extra_semis_inside(self): + s1 = """ + struct { + int a;; + } foo; + """ + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[0]), + ['Decl', 'foo', + ['TypeDecl', ['Struct', None, + [['Decl', 'a', + ['TypeDecl', ['IdentifierType', ['int']]]]]]]]) + + s2 = """ + struct { + int a;;;; + float b, c; + ;; + char d; + } foo; + """ + s2_ast = self.parse(s2) + self.assertEqual(expand_decl(s2_ast.ext[0]), + ['Decl', 'foo', + ['TypeDecl', ['Struct', None, + [['Decl', 'a', ['TypeDecl', ['IdentifierType', ['int']]]], + ['Decl', 'b', ['TypeDecl', ['IdentifierType', ['float']]]], + ['Decl', 'c', ['TypeDecl', ['IdentifierType', ['float']]]], + ['Decl', 'd', + ['TypeDecl', ['IdentifierType', ['char']]]]]]]]) + + def test_anonymous_struct_union(self): + s1 = """ + union + { + union + { + int i; + long l; + }; + + struct + { + int type; + int intnode; + }; + } u; + """ + + self.assertEqual(expand_decl(self.parse(s1).ext[0]), + ['Decl', 'u', + ['TypeDecl', + ['Union', None, + [['Decl', None, + ['Union', None, + [['Decl', 'i', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', 'l', + ['TypeDecl', + ['IdentifierType', ['long']]]]]]], + ['Decl', None, + ['Struct', None, + [['Decl', 'type', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', 'intnode', + ['TypeDecl', + ['IdentifierType', ['int']]]]]]]]]]]) + + s2 = """ + struct + { + int i; + union + { + int id; + char* name; + }; + float f; + } joe; + """ + + self.assertEqual(expand_decl(self.parse(s2).ext[0]), + ['Decl', 'joe', + ['TypeDecl', + ['Struct', None, + [['Decl', 'i', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', None, + ['Union', None, + [['Decl', 'id', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', 'name', + ['PtrDecl', + ['TypeDecl', + ['IdentifierType', ['char']]]]]]]], + ['Decl', 'f', + ['TypeDecl', + ['IdentifierType', ['float']]]]]]]]) + + # ISO/IEC 9899:201x Commitee Draft 2010-11-16, N1539 + # section 6.7.2.1, par. 19, example 1 + s3 = """ + struct v { + union { + struct { int i, j; }; + struct { long k, l; } w; + }; + int m; + } v1; + """ + + self.assertEqual(expand_decl(self.parse(s3).ext[0]), + ['Decl', 'v1', + ['TypeDecl', + ['Struct', 'v', + [['Decl', None, + ['Union', None, + [['Decl', None, + ['Struct', None, + [['Decl', 'i', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', 'j', + ['TypeDecl', + ['IdentifierType', ['int']]]]]]], + ['Decl', 'w', + ['TypeDecl', + ['Struct', None, + [['Decl', 'k', + ['TypeDecl', + ['IdentifierType', ['long']]]], + ['Decl', 'l', + ['TypeDecl', + ['IdentifierType', ['long']]]]]]]]]]], + ['Decl', 'm', + ['TypeDecl', + ['IdentifierType', ['int']]]]]]]]) + + s4 = """ + struct v { + int i; + float; + } v2;""" + # just make sure this doesn't raise ParseError + self.parse(s4) + + def test_struct_members_namespace(self): + """ Tests that structure/union member names reside in a separate + namespace and can be named after existing types. + """ + s1 = """ + typedef int Name; + typedef Name NameArray[10]; + + struct { + Name Name; + Name NameArray[3]; + } sye; + + void main(void) + { + sye.Name = 1; + } + """ + + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[2]), + ['Decl', 'sye', + ['TypeDecl', ['Struct', None, + [ ['Decl', 'Name', + ['TypeDecl', + ['IdentifierType', ['Name']]]], + ['Decl', 'NameArray', + ['ArrayDecl', '3', [], + ['TypeDecl', ['IdentifierType', ['Name']]]]]]]]]) + self.assertEqual(s1_ast.ext[3].body.block_items[0].lvalue.field.name, 'Name') + + def test_struct_bitfields(self): + # a struct with two bitfields, one unnamed + s1 = """ + struct { + int k:6; + int :2; + } joe; + """ + + parsed_struct = self.parse(s1).ext[0] + + # We can see here the name of the decl for the unnamed bitfield is + # None, but expand_decl doesn't show bitfield widths + # ... + self.assertEqual(expand_decl(parsed_struct), + ['Decl', 'joe', + ['TypeDecl', ['Struct', None, + [ ['Decl', 'k', + ['TypeDecl', + ['IdentifierType', ['int']]]], + ['Decl', None, + ['TypeDecl', + ['IdentifierType', ['int']]]]]]]]) + + # ... + # so we test them manually + self.assertEqual(parsed_struct.type.type.decls[0].bitsize.value, '6') + self.assertEqual(parsed_struct.type.type.decls[1].bitsize.value, '2') + + def test_struct_empty(self): + """ + Tests that parsing an empty struct works. + + Empty structs do NOT follow C99 (See 6.2.5-20 of the C99 standard). + This is nevertheless supported by some compilers (clang, gcc), + especially when using FORTIFY code. + Some compilers (visual) will fail to compile with an error. + """ + # an empty struct. This is NOT C99 compliant + s1 = """ + struct foo { }; + """ + + parsed_struct = self.parse(s1).ext[0] + self.assertEqual(expand_decl(parsed_struct), + ['Decl', None, ['Struct', 'foo', []]]) + + s2 = """struct { } foo;""" + parsed_struct = self.parse(s2).ext[0] + self.assertEqual(expand_decl(parsed_struct), + ['Decl', 'foo', ['TypeDecl', ['Struct', None, []]]]) + + s3 = """union { } foo;""" + parsed_struct = self.parse(s3).ext[0] + self.assertEqual(expand_decl(parsed_struct), + ['Decl', 'foo', ['TypeDecl', ['Union', None, []]]]) + + def test_tags_namespace(self): + """ Tests that the tags of structs/unions/enums reside in a separate namespace and + can be named after existing types. + """ + s1 = """ + typedef int tagEntry; + + struct tagEntry + { + char* key; + char* value; + } Entry; + """ + + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[1]), + ['Decl', 'Entry', + ['TypeDecl', ['Struct', 'tagEntry', + [['Decl', 'key', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]], + ['Decl', 'value', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]]]]]]) + + s2 = """ + struct tagEntry; + + typedef struct tagEntry tagEntry; + + struct tagEntry + { + char* key; + char* value; + } Entry; + """ + + s2_ast = self.parse(s2) + self.assertEqual(expand_decl(s2_ast.ext[2]), + ['Decl', 'Entry', + ['TypeDecl', ['Struct', 'tagEntry', + [['Decl', 'key', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]], + ['Decl', 'value', + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]]]]]]) + + s3 = """ + typedef int mytag; + + enum mytag {ABC, CDE}; + enum mytag joe; + """ + + s3_type = self.parse(s3).ext[1].type + + self.assertIsInstance(s3_type, Enum) + self.assertEqual(s3_type.name, 'mytag') + + def test_multi_decls(self): + d1 = 'int a, b;' + + self.assertEqual(self.get_decl(d1, 0), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(self.get_decl(d1, 1), + ['Decl', 'b', ['TypeDecl', ['IdentifierType', ['int']]]]) + + d2 = 'char* p, notp, ar[4];' + self.assertEqual(self.get_decl(d2, 0), + ['Decl', 'p', + ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]) + self.assertEqual(self.get_decl(d2, 1), + ['Decl', 'notp', ['TypeDecl', ['IdentifierType', ['char']]]]) + self.assertEqual(self.get_decl(d2, 2), + ['Decl', 'ar', + ['ArrayDecl', '4', [], + ['TypeDecl', ['IdentifierType', ['char']]]]]) + + def test_invalid_multiple_types_error(self): + bad = [ + 'int enum {ab, cd} fubr;', + 'enum kid char brbr;'] + + for b in bad: + self.assertRaises(ParseError, self.parse, b) + + def test_duplicate_typedef(self): + """ Tests that redeclarations of existing types are parsed correctly. + This is non-standard, but allowed by many compilers. + """ + d1 = ''' + typedef int numbertype; + typedef int numbertype; + ''' + + self.assertEqual(self.get_decl(d1, 0), + ['Typedef', 'numbertype', + ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(self.get_decl(d1, 1), + ['Typedef', 'numbertype', + ['TypeDecl', ['IdentifierType', ['int']]]]) + + d2 = ''' + typedef int (*funcptr)(int x); + typedef int (*funcptr)(int x); + ''' + self.assertEqual(self.get_decl(d2, 0), + ['Typedef', 'funcptr', + ['PtrDecl', ['FuncDecl', + [['Decl', 'x', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + self.assertEqual(self.get_decl(d2, 1), + ['Typedef', 'funcptr', + ['PtrDecl', ['FuncDecl', + [['Decl', 'x', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]]) + + d3 = ''' + typedef int numberarray[5]; + typedef int numberarray[5]; + ''' + self.assertEqual(self.get_decl(d3, 0), + ['Typedef', 'numberarray', + ['ArrayDecl', '5', [], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + self.assertEqual(self.get_decl(d3, 1), + ['Typedef', 'numberarray', + ['ArrayDecl', '5', [], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + def test_decl_inits(self): + d1 = 'int a = 16;' + #~ self.parse(d1).show() + self.assertEqual(self.get_decl(d1), + ['Decl', 'a', ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(self.get_decl_init(d1), + ['Constant', 'int', '16']) + + d1_1 = 'float f = 0xEF.56p1;' + self.assertEqual(self.get_decl_init(d1_1), + ['Constant', 'float', '0xEF.56p1']) + + d1_2 = 'int bitmask = 0b1001010;' + self.assertEqual(self.get_decl_init(d1_2), + ['Constant', 'int', '0b1001010']) + + d2 = 'long ar[] = {7, 8, 9};' + self.assertEqual(self.get_decl(d2), + ['Decl', 'ar', + ['ArrayDecl', '', [], + ['TypeDecl', ['IdentifierType', ['long']]]]]) + self.assertEqual(self.get_decl_init(d2), + [ ['Constant', 'int', '7'], + ['Constant', 'int', '8'], + ['Constant', 'int', '9']]) + + d21 = 'long ar[4] = {};' + self.assertEqual(self.get_decl_init(d21), []) + + d3 = 'char p = j;' + self.assertEqual(self.get_decl(d3), + ['Decl', 'p', ['TypeDecl', ['IdentifierType', ['char']]]]) + self.assertEqual(self.get_decl_init(d3), + ['ID', 'j']) + + d4 = "char x = 'c', *p = {0, 1, 2, {4, 5}, 6};" + self.assertEqual(self.get_decl(d4, 0), + ['Decl', 'x', ['TypeDecl', ['IdentifierType', ['char']]]]) + self.assertEqual(self.get_decl_init(d4, 0), + ['Constant', 'char', "'c'"]) + self.assertEqual(self.get_decl(d4, 1), + ['Decl', 'p', + ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]) + + self.assertEqual(self.get_decl_init(d4, 1), + [ ['Constant', 'int', '0'], + ['Constant', 'int', '1'], + ['Constant', 'int', '2'], + [['Constant', 'int', '4'], + ['Constant', 'int', '5']], + ['Constant', 'int', '6']]) + + d5 = 'float d = 1.0;' + self.assertEqual(self.get_decl_init(d5), + ['Constant', 'double', '1.0']) + + d51 = 'float ld = 1.0l;' + self.assertEqual(self.get_decl_init(d51), + ['Constant', 'long double', '1.0l']) + + d52 = 'float ld = 1.0L;' + self.assertEqual(self.get_decl_init(d52), + ['Constant', 'long double', '1.0L']) + + d53 = 'float ld = 1.0f;' + self.assertEqual(self.get_decl_init(d53), + ['Constant', 'float', '1.0f']) + + d54 = 'float ld = 1.0F;' + self.assertEqual(self.get_decl_init(d54), + ['Constant', 'float', '1.0F']) + + d55 = 'float ld = 0xDE.38p0;' + self.assertEqual(self.get_decl_init(d55), + ['Constant', 'float', '0xDE.38p0']) + + def test_decl_named_inits(self): + d1 = 'int a = {.k = 16};' + self.assertEqual(self.get_decl_init(d1), + [( [['ID', 'k']], + ['Constant', 'int', '16'])]) + + d2 = 'int a = { [0].a = {1}, [1].a[0] = 2 };' + self.assertEqual(self.get_decl_init(d2), + [ + ([['Constant', 'int', '0'], ['ID', 'a']], + [['Constant', 'int', '1']]), + ([['Constant', 'int', '1'], ['ID', 'a'], ['Constant', 'int', '0']], + ['Constant', 'int', '2'])]) + + d3 = 'int a = { .a = 1, .c = 3, 4, .b = 5};' + self.assertEqual(self.get_decl_init(d3), + [ + ([['ID', 'a']], ['Constant', 'int', '1']), + ([['ID', 'c']], ['Constant', 'int', '3']), + ['Constant', 'int', '4'], + ([['ID', 'b']], ['Constant', 'int', '5'])]) + + def test_function_definitions(self): + def parse_fdef(str): + return self.parse(str).ext[0] + + def fdef_decl(fdef): + return expand_decl(fdef.decl) + + f1 = parse_fdef(''' + int factorial(int p) + { + return 3; + } + ''') + + self.assertEqual(fdef_decl(f1), + ['Decl', 'factorial', + ['FuncDecl', + [['Decl', 'p', ['TypeDecl', ['IdentifierType', ['int']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + self.assertEqual(type(f1.body.block_items[0]), Return) + + f2 = parse_fdef(''' + char* zzz(int p, char* c) + { + int a; + char b; + + a = b + 2; + return 3; + } + ''') + + self.assertEqual(fdef_decl(f2), + ['Decl', 'zzz', + ['FuncDecl', + [ ['Decl', 'p', ['TypeDecl', ['IdentifierType', ['int']]]], + ['Decl', 'c', ['PtrDecl', + ['TypeDecl', ['IdentifierType', ['char']]]]]], + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]]]) + + self.assertEqual(list(map(type, f2.body.block_items)), + [Decl, Decl, Assignment, Return]) + + f3 = parse_fdef(''' + char* zzz(p, c) + long p, *c; + { + int a; + char b; + + a = b + 2; + return 3; + } + ''') + + self.assertEqual(fdef_decl(f3), + ['Decl', 'zzz', + ['FuncDecl', + [ ['ID', 'p'], + ['ID', 'c']], + ['PtrDecl', ['TypeDecl', ['IdentifierType', ['char']]]]]]) + + self.assertEqual(list(map(type, f3.body.block_items)), + [Decl, Decl, Assignment, Return]) + + self.assertEqual(expand_decl(f3.param_decls[0]), + ['Decl', 'p', ['TypeDecl', ['IdentifierType', ['long']]]]) + self.assertEqual(expand_decl(f3.param_decls[1]), + ['Decl', 'c', ['PtrDecl', ['TypeDecl', ['IdentifierType', ['long']]]]]) + + # function return values and parameters may not have type information + f4 = parse_fdef(''' + que(p) + { + return 3; + } + ''') + + self.assertEqual(fdef_decl(f4), + ['Decl', 'que', + ['FuncDecl', + [['ID', 'p']], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + def test_unified_string_literals(self): + # simple string, for reference + d1 = self.get_decl_init('char* s = "hello";') + self.assertEqual(d1, ['Constant', 'string', '"hello"']) + + d2 = self.get_decl_init('char* s = "hello" " world";') + self.assertEqual(d2, ['Constant', 'string', '"hello world"']) + + # the test case from issue 6 + d3 = self.parse(r''' + int main() { + fprintf(stderr, + "Wrong Params?\n" + "Usage:\n" + "%s <binary_file_path>\n", + argv[0] + ); + } + ''') + + self.assertEqual( + d3.ext[0].body.block_items[0].args.exprs[1].value, + r'"Wrong Params?\nUsage:\n%s <binary_file_path>\n"') + + d4 = self.get_decl_init('char* s = "" "foobar";') + self.assertEqual(d4, ['Constant', 'string', '"foobar"']) + + d5 = self.get_decl_init(r'char* s = "foo\"" "bar";') + self.assertEqual(d5, ['Constant', 'string', r'"foo\"bar"']) + + def test_unified_wstring_literals(self): + d1 = self.get_decl_init('char* s = L"hello" L"world";') + self.assertEqual(d1, ['Constant', 'string', 'L"helloworld"']) + + d2 = self.get_decl_init('char* s = L"hello " L"world" L" and I";') + self.assertEqual(d2, ['Constant', 'string', 'L"hello world and I"']) + + def test_inline_specifier(self): + ps2 = self.parse('static inline void inlinefoo(void);') + self.assertEqual(ps2.ext[0].funcspec, ['inline']) + + # variable length array + def test_vla(self): + ps2 = self.parse(r''' + int main() { + int size; + int var[size = 5]; + + int var2[*]; + } + ''') + self.assertIsInstance(ps2.ext[0].body.block_items[1].type.dim, Assignment) + self.assertIsInstance(ps2.ext[0].body.block_items[2].type.dim, ID) + + def test_pragma(self): + s1 = r''' + #pragma bar + void main() { + #pragma foo + for(;;) {} + #pragma + } + struct s { + #pragma baz + } s; + ''' + s1_ast = self.parse(s1) + self.assertIsInstance(s1_ast.ext[0], Pragma) + self.assertEqual(s1_ast.ext[0].string, 'bar') + self.assertEqual(s1_ast.ext[0].coord.line, 2) + + self.assertIsInstance(s1_ast.ext[1].body.block_items[0], Pragma) + self.assertEqual(s1_ast.ext[1].body.block_items[0].string, 'foo') + self.assertEqual(s1_ast.ext[1].body.block_items[0].coord.line, 4) + + self.assertIsInstance(s1_ast.ext[1].body.block_items[2], Pragma) + self.assertEqual(s1_ast.ext[1].body.block_items[2].string, '') + self.assertEqual(s1_ast.ext[1].body.block_items[2].coord.line, 6) + + self.assertIsInstance(s1_ast.ext[2].type.type.decls[0], Pragma) + self.assertEqual(s1_ast.ext[2].type.type.decls[0].string, 'baz') + self.assertEqual(s1_ast.ext[2].type.type.decls[0].coord.line, 9) + + def test_pragmacomp_or_statement(self): + s1 = r''' + void main() { + int sum = 0; + for (int i; i < 3; i++) + #pragma omp critical + sum += 1; + + while(sum < 10) + #pragma omp critical + sum += 1; + + mylabel: + #pragma foo + sum += 10; + + if (sum > 10) + #pragma bar + sum = 10; + + switch (sum) + case 10: + #pragma foo + sum = 20; + } + ''' + s1_ast = self.parse(s1) + self.assertIsInstance(s1_ast.ext[0].body.block_items[1], For) + self.assertIsInstance(s1_ast.ext[0].body.block_items[1].stmt, Compound) + self.assertIsInstance(s1_ast.ext[0].body.block_items[1].stmt.block_items[0], Pragma) + self.assertIsInstance(s1_ast.ext[0].body.block_items[1].stmt.block_items[1], Assignment) + self.assertIsInstance(s1_ast.ext[0].body.block_items[2], While) + self.assertIsInstance(s1_ast.ext[0].body.block_items[2].stmt, Compound) + self.assertIsInstance(s1_ast.ext[0].body.block_items[2].stmt.block_items[0], Pragma) + self.assertIsInstance(s1_ast.ext[0].body.block_items[2].stmt.block_items[1], Assignment) + self.assertIsInstance(s1_ast.ext[0].body.block_items[3], Label) + self.assertIsInstance(s1_ast.ext[0].body.block_items[3].stmt, Compound) + self.assertIsInstance(s1_ast.ext[0].body.block_items[3].stmt.block_items[0], Pragma) + self.assertIsInstance(s1_ast.ext[0].body.block_items[3].stmt.block_items[1], Assignment) + self.assertIsInstance(s1_ast.ext[0].body.block_items[4], If) + self.assertIsInstance(s1_ast.ext[0].body.block_items[4].iftrue, Compound) + self.assertIsInstance(s1_ast.ext[0].body.block_items[4].iftrue.block_items[0], Pragma) + self.assertIsInstance(s1_ast.ext[0].body.block_items[4].iftrue.block_items[1], Assignment) + self.assertIsInstance(s1_ast.ext[0].body.block_items[5], Switch) + self.assertIsInstance(s1_ast.ext[0].body.block_items[5].stmt.stmts[0], Compound) + self.assertIsInstance(s1_ast.ext[0].body.block_items[5].stmt.stmts[0].block_items[0], Pragma) + self.assertIsInstance(s1_ast.ext[0].body.block_items[5].stmt.stmts[0].block_items[1], Assignment) + + +class TestCParser_whole_code(TestCParser_base): + """ Testing of parsing whole chunks of code. + + Since I don't want to rely on the structure of ASTs too + much, most of these tests are implemented with visitors. + """ + # A simple helper visitor that lists the values of all the + # Constant nodes it sees. + # + class ConstantVisitor(NodeVisitor): + def __init__(self): + self.values = [] + + def visit_Constant(self, node): + self.values.append(node.value) + + # This visitor counts the amount of references to the ID + # with the name provided to it in the constructor. + # + class IDNameCounter(NodeVisitor): + def __init__(self, name): + self.name = name + self.nrefs = 0 + + def visit_ID(self, node): + if node.name == self.name: + self.nrefs += 1 + + # Counts the amount of nodes of a given class + # + class NodeKlassCounter(NodeVisitor): + def __init__(self, node_klass): + self.klass = node_klass + self.n = 0 + + def generic_visit(self, node): + if node.__class__ == self.klass: + self.n += 1 + + NodeVisitor.generic_visit(self, node) + + def assert_all_Constants(self, code, constants): + """ Asserts that the list of all Constant values (by + 'preorder' appearance) in the chunk of code is as + given. + """ + if isinstance(code, str): + parsed = self.parse(code) + else: + parsed = code + + cv = self.ConstantVisitor() + cv.visit(parsed) + self.assertEqual(cv.values, constants) + + def assert_num_ID_refs(self, code, name, num): + """ Asserts the number of references to the ID with + the given name. + """ + if isinstance(code, str): + parsed = self.parse(code) + else: + parsed = code + + iv = self.IDNameCounter(name) + iv.visit(parsed) + self.assertEqual(iv.nrefs, num) + + def assert_num_klass_nodes(self, code, klass, num): + """ Asserts the amount of klass nodes in the code. + """ + if isinstance(code, str): + parsed = self.parse(code) + else: + parsed = code + + cv = self.NodeKlassCounter(klass) + cv.visit(parsed) + self.assertEqual(cv.n, num) + + def test_expressions(self): + e1 = '''int k = (r + 10.0) >> 6 + 8 << (3 & 0x14);''' + self.assert_all_Constants(e1, ['10.0', '6', '8', '3', '0x14']) + + e2 = r'''char n = '\n', *prefix = "st_";''' + self.assert_all_Constants(e2, [r"'\n'", '"st_"']) + + s1 = r'''int main() { + int i = 5, j = 6, k = 1; + if ((i=j && k == 1) || k > j) + printf("Hello, world\n"); + return 0; + }''' + ps1 = self.parse(s1) + self.assert_all_Constants(ps1, + ['5', '6', '1', '1', '"Hello, world\\n"', '0']) + self.assert_num_ID_refs(ps1, 'i', 1) + self.assert_num_ID_refs(ps1, 'j', 2) + + + def test_statements(self): + s1 = r''' + void foo(){ + if (sp == 1) + if (optind >= argc || + argv[optind][0] != '-' || argv[optind][1] == '\0') + return -1; + else if (strcmp(argv[optind], "--") == 0) { + optind++; + return -1; + } + } + ''' + + self.assert_all_Constants(s1, + ['1', '0', r"'-'", '1', r"'\0'", '1', r'"--"', '0', '1']) + + ps1 = self.parse(s1) + self.assert_num_ID_refs(ps1, 'argv', 3) + self.assert_num_ID_refs(ps1, 'optind', 5) + + self.assert_num_klass_nodes(ps1, If, 3) + self.assert_num_klass_nodes(ps1, Return, 2) + self.assert_num_klass_nodes(ps1, FuncCall, 1) # strcmp + self.assert_num_klass_nodes(ps1, BinaryOp, 7) + + # In the following code, Hash and Node were defined as + # int to pacify the parser that sees they're used as + # types + # + s2 = r''' + typedef int Hash, Node; + + void HashDestroy(Hash* hash) + { + unsigned int i; + + if (hash == NULL) + return; + + for (i = 0; i < hash->table_size; ++i) + { + Node* temp = hash->heads[i]; + + while (temp != NULL) + { + Node* temp2 = temp; + + free(temp->entry->key); + free(temp->entry->value); + free(temp->entry); + + temp = temp->next; + + free(temp2); + } + } + + free(hash->heads); + hash->heads = NULL; + + free(hash); + } + ''' + + ps2 = self.parse(s2) + self.assert_num_klass_nodes(ps2, FuncCall, 6) + self.assert_num_klass_nodes(ps2, FuncDef, 1) + self.assert_num_klass_nodes(ps2, For, 1) + self.assert_num_klass_nodes(ps2, While, 1) + self.assert_num_klass_nodes(ps2, StructRef, 10) + + # declarations don't count + self.assert_num_ID_refs(ps2, 'hash', 6) + self.assert_num_ID_refs(ps2, 'i', 4) + + s3 = r''' + void x(void) { + int a, b; + if (a < b) + do { + a = 0; + } while (0); + else if (a == b) { + a = 1; + } + } + ''' + + ps3 = self.parse(s3) + self.assert_num_klass_nodes(ps3, DoWhile, 1) + self.assert_num_ID_refs(ps3, 'a', 4) + self.assert_all_Constants(ps3, ['0', '0', '1']) + + def test_empty_statements(self): + s1 = r''' + void foo(void){ + ; + return;; + + ; + } + ''' + ps1 = self.parse(s1) + self.assert_num_klass_nodes(ps1, EmptyStatement, 3) + self.assert_num_klass_nodes(ps1, Return, 1) + self.assert_coord(ps1.ext[0].body.block_items[0], 3) + self.assert_coord(ps1.ext[0].body.block_items[1], 4) + self.assert_coord(ps1.ext[0].body.block_items[2], 4) + self.assert_coord(ps1.ext[0].body.block_items[3], 6) + + def test_switch_statement(self): + def assert_case_node(node, const_value): + self.assertIsInstance(node, Case) + self.assertIsInstance(node.expr, Constant) + self.assertEqual(node.expr.value, const_value) + + def assert_default_node(node): + self.assertIsInstance(node, Default) + + s1 = r''' + int foo(void) { + switch (myvar) { + case 10: + k = 10; + p = k + 1; + return 10; + case 20: + case 30: + return 20; + default: + break; + } + return 0; + } + ''' + ps1 = self.parse(s1) + switch = ps1.ext[0].body.block_items[0] + + block = switch.stmt.block_items + assert_case_node(block[0], '10') + self.assertEqual(len(block[0].stmts), 3) + assert_case_node(block[1], '20') + self.assertEqual(len(block[1].stmts), 0) + assert_case_node(block[2], '30') + self.assertEqual(len(block[2].stmts), 1) + assert_default_node(block[3]) + + s2 = r''' + int foo(void) { + switch (myvar) { + default: + joe = moe; + return 10; + case 10: + case 20: + case 30: + case 40: + break; + } + return 0; + } + ''' + ps2 = self.parse(s2) + switch = ps2.ext[0].body.block_items[0] + + block = switch.stmt.block_items + assert_default_node(block[0]) + self.assertEqual(len(block[0].stmts), 2) + assert_case_node(block[1], '10') + self.assertEqual(len(block[1].stmts), 0) + assert_case_node(block[2], '20') + self.assertEqual(len(block[1].stmts), 0) + assert_case_node(block[3], '30') + self.assertEqual(len(block[1].stmts), 0) + assert_case_node(block[4], '40') + self.assertEqual(len(block[4].stmts), 1) + + def test_for_statement(self): + s2 = r''' + void x(void) + { + int i; + for (i = 0; i < 5; ++i) { + x = 50; + } + } + ''' + ps2 = self.parse(s2) + self.assert_num_klass_nodes(ps2, For, 1) + # here there are 3 refs to 'i' since the declaration doesn't count as + # a ref in the visitor + # + self.assert_num_ID_refs(ps2, 'i', 3) + + s3 = r''' + void x(void) + { + for (int i = 0; i < 5; ++i) { + x = 50; + } + } + ''' + ps3 = self.parse(s3) + self.assert_num_klass_nodes(ps3, For, 1) + # here there are 2 refs to 'i' since the declaration doesn't count as + # a ref in the visitor + # + self.assert_num_ID_refs(ps3, 'i', 2) + + s4 = r''' + void x(void) { + for (int i = 0;;) + i; + } + ''' + ps4 = self.parse(s4) + self.assert_num_ID_refs(ps4, 'i', 1) + + def _open_c_file(self, name): + """ Find a c file by name, taking into account the current dir can be + in a couple of typical places + """ + testdir = os.path.dirname(__file__) + name = os.path.join(testdir, 'c_files', name) + assert os.path.exists(name) + return io.open(name) + + def test_whole_file(self): + # See how pycparser handles a whole, real C file. + # + with self._open_c_file('memmgr_with_h.c') as f: + code = f.read() + p = self.parse(code) + + self.assert_num_klass_nodes(p, FuncDef, 5) + + # each FuncDef also has a FuncDecl. 4 declarations + # + 5 definitions, overall 9 + self.assert_num_klass_nodes(p, FuncDecl, 9) + + self.assert_num_klass_nodes(p, Typedef, 4) + + self.assertEqual(p.ext[4].coord.line, 88) + self.assertEqual(p.ext[4].coord.file, "./memmgr.h") + + self.assertEqual(p.ext[6].coord.line, 10) + self.assertEqual(p.ext[6].coord.file, "memmgr.c") + + def test_whole_file_with_stdio(self): + # Parse a whole file with stdio.h included by cpp + # + with self._open_c_file('cppd_with_stdio_h.c') as f: + code = f.read() + p = self.parse(code) + + self.assertIsInstance(p.ext[0], Typedef) + self.assertEqual(p.ext[0].coord.line, 213) + self.assertEqual(p.ext[0].coord.file, r"D:\eli\cpp_stuff\libc_include/stddef.h") + + self.assertIsInstance(p.ext[-1], FuncDef) + self.assertEqual(p.ext[-1].coord.line, 15) + self.assertEqual(p.ext[-1].coord.file, "example_c_file.c") + + self.assertIsInstance(p.ext[-8], Typedef) + self.assertIsInstance(p.ext[-8].type, TypeDecl) + self.assertEqual(p.ext[-8].name, 'cookie_io_functions_t') + + +class TestCParser_typenames(TestCParser_base): + """ Test issues related to the typedef-name problem. + """ + def test_innerscope_typedef(self): + # should fail since TT is not a type in bar + s1 = r''' + void foo() { + typedef char TT; + TT x; + } + void bar() { + TT y; + } + ''' + self.assertRaises(ParseError, self.parse, s1) + + # should succeed since TT is not a type in bar + s2 = r''' + void foo() { + typedef char TT; + TT x; + } + void bar() { + unsigned TT; + } + ''' + self.assertIsInstance(self.parse(s2), FileAST) + + def test_ambiguous_parameters(self): + # From ISO/IEC 9899:TC2, 6.7.5.3.11: + # "If, in a parameter declaration, an identifier can be treated either + # as a typedef name or as a parameter name, it shall be taken as a + # typedef name." + + # foo takes an int named aa + # bar takes a function taking a TT + s1 = r''' + typedef char TT; + int foo(int (aa)); + int bar(int (TT)); + ''' + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[1].type.args.params[0]), + ['Decl', 'aa', ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(expand_decl(s1_ast.ext[2].type.args.params[0]), + ['Typename', ['FuncDecl', + [['Typename', ['TypeDecl', ['IdentifierType', ['TT']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + # foo takes a function taking a char + # bar takes a function taking a function taking a char + s2 = r''' + typedef char TT; + int foo(int (aa (char))); + int bar(int (TT (char))); + ''' + s2_ast = self.parse(s2) + self.assertEqual(expand_decl(s2_ast.ext[1].type.args.params[0]), + ['Decl', 'aa', ['FuncDecl', + [['Typename', ['TypeDecl', ['IdentifierType', ['char']]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + self.assertEqual(expand_decl(s2_ast.ext[2].type.args.params[0]), + ['Typename', ['FuncDecl', + [['Typename', ['FuncDecl', + [['Typename', ['TypeDecl', ['IdentifierType', ['char']]]]], + ['TypeDecl', ['IdentifierType', ['TT']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + + # foo takes an int array named aa + # bar takes a function taking a TT array + s3 = r''' + typedef char TT; + int foo(int (aa[])); + int bar(int (TT[])); + ''' + s3_ast = self.parse(s3) + self.assertEqual(expand_decl(s3_ast.ext[1].type.args.params[0]), + ['Decl', 'aa', ['ArrayDecl', '', [], ['TypeDecl', ['IdentifierType', ['int']]]]]) + self.assertEqual(expand_decl(s3_ast.ext[2].type.args.params[0]), + ['Typename', ['FuncDecl', + [['Typename', ['ArrayDecl', '', [], ['TypeDecl', ['IdentifierType', ['TT']]]]]], + ['TypeDecl', ['IdentifierType', ['int']]]]]) + + def test_innerscope_reuse_typedef_name(self): + # identifiers can be reused in inner scopes; the original should be + # restored at the end of the block + s1 = r''' + typedef char TT; + void foo(void) { + unsigned TT; + TT = 10; + } + TT x = 5; + ''' + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[1].body.block_items[0]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + self.assertEqual(expand_decl(s1_ast.ext[2]), + ['Decl', 'x', ['TypeDecl', ['IdentifierType', ['TT']]]]) + + # this should be recognized even with an initializer + s2 = r''' + typedef char TT; + void foo(void) { + unsigned TT = 10; + } + ''' + s2_ast = self.parse(s2) + self.assertEqual(expand_decl(s2_ast.ext[1].body.block_items[0]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + + # before the second local variable, TT is a type; after, it's a + # variable + s3 = r''' + typedef char TT; + void foo(void) { + TT tt = sizeof(TT); + unsigned TT = 10; + } + ''' + s3_ast = self.parse(s3) + self.assertEqual(expand_decl(s3_ast.ext[1].body.block_items[0]), + ['Decl', 'tt', ['TypeDecl', ['IdentifierType', ['TT']]]]) + self.assertEqual(expand_decl(s3_ast.ext[1].body.block_items[1]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + + # a variable and its type can even share the same name + s4 = r''' + typedef char TT; + void foo(void) { + TT TT = sizeof(TT); + unsigned uu = TT * 2; + } + ''' + s4_ast = self.parse(s4) + self.assertEqual(expand_decl(s4_ast.ext[1].body.block_items[0]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['TT']]]]) + self.assertEqual(expand_decl(s4_ast.ext[1].body.block_items[1]), + ['Decl', 'uu', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + + # ensure an error is raised if a type, redeclared as a variable, is + # used as a type + s5 = r''' + typedef char TT; + void foo(void) { + unsigned TT = 10; + TT erroneous = 20; + } + ''' + self.assertRaises(ParseError, self.parse, s5) + + # reusing a type name should work with multiple declarators + s6 = r''' + typedef char TT; + void foo(void) { + unsigned TT, uu; + } + ''' + s6_ast = self.parse(s6) + items = s6_ast.ext[1].body.block_items + self.assertEqual(expand_decl(items[0]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + self.assertEqual(expand_decl(items[1]), + ['Decl', 'uu', ['TypeDecl', ['IdentifierType', ['unsigned']]]]) + + # reusing a type name should work after a pointer + s7 = r''' + typedef char TT; + void foo(void) { + unsigned * TT; + } + ''' + s7_ast = self.parse(s7) + items = s7_ast.ext[1].body.block_items + self.assertEqual(expand_decl(items[0]), + ['Decl', 'TT', ['PtrDecl', ['TypeDecl', ['IdentifierType', ['unsigned']]]]]) + + # redefine a name in the middle of a multi-declarator declaration + s8 = r''' + typedef char TT; + void foo(void) { + int tt = sizeof(TT), TT, uu = sizeof(TT); + int uu = sizeof(tt); + } + ''' + s8_ast = self.parse(s8) + items = s8_ast.ext[1].body.block_items + self.assertEqual(expand_decl(items[0]), + ['Decl', 'tt', ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(expand_decl(items[1]), + ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['int']]]]) + self.assertEqual(expand_decl(items[2]), + ['Decl', 'uu', ['TypeDecl', ['IdentifierType', ['int']]]]) + + # Don't test this until we have support for it + # self.assertEqual(expand_init(items[0].init), + # ['UnaryOp', 'sizeof', ['Typename', ['TypeDecl', ['IdentifierType', ['TT']]]]]) + # self.assertEqual(expand_init(items[2].init), + # ['UnaryOp', 'sizeof', ['ID', 'TT']]) + + def test_parameter_reuse_typedef_name(self): + # identifiers can be reused as parameter names; parameter name scope + # begins and ends with the function body; it's important that TT is + # used immediately before the LBRACE or after the RBRACE, to test + # a corner case + s1 = r''' + typedef char TT; + void foo(unsigned TT, TT bar) { + TT = 10; + } + TT x = 5; + ''' + s1_ast = self.parse(s1) + self.assertEqual(expand_decl(s1_ast.ext[1].decl), + ['Decl', 'foo', + ['FuncDecl', + [ ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]], + ['Decl', 'bar', ['TypeDecl', ['IdentifierType', ['TT']]]]], + ['TypeDecl', ['IdentifierType', ['void']]]]]) + + # the scope of a parameter name in a function declaration ends at the + # end of the declaration...so it is effectively never used; it's + # important that TT is used immediately after the declaration, to + # test a corner case + s2 = r''' + typedef char TT; + void foo(unsigned TT, TT bar); + TT x = 5; + ''' + s2_ast = self.parse(s2) + self.assertEqual(expand_decl(s2_ast.ext[1]), + ['Decl', 'foo', + ['FuncDecl', + [ ['Decl', 'TT', ['TypeDecl', ['IdentifierType', ['unsigned']]]], + ['Decl', 'bar', ['TypeDecl', ['IdentifierType', ['TT']]]]], + ['TypeDecl', ['IdentifierType', ['void']]]]]) + + # ensure an error is raised if a type, redeclared as a parameter, is + # used as a type + s3 = r''' + typedef char TT; + void foo(unsigned TT, TT bar) { + TT erroneous = 20; + } + ''' + self.assertRaises(ParseError, self.parse, s3) + + def test_nested_function_decls(self): + # parameter names of nested function declarations must not escape into + # the top-level function _definition's_ scope; the following must + # succeed because TT is still a typedef inside foo's body + s1 = r''' + typedef char TT; + void foo(unsigned bar(int TT)) { + TT x = 10; + } + ''' + self.assertIsInstance(self.parse(s1), FileAST) + + def test_samescope_reuse_name(self): + # a typedef name cannot be reused as an object name in the same scope + s1 = r''' + typedef char TT; + char TT = 5; + ''' + self.assertRaises(ParseError, self.parse, s1) + + # ...and vice-versa + s2 = r''' + char TT = 5; + typedef char TT; + ''' + self.assertRaises(ParseError, self.parse, s2) + + +if __name__ == '__main__': + #~ suite = unittest.TestLoader().loadTestsFromNames( + #~ ['test_c_parser.TestCParser_fundamentals.test_typedef']) + + #~ unittest.TextTestRunner(verbosity=2).run(suite) + unittest.main() diff --git a/tests/test_general.py b/tests/test_general.py new file mode 100644 index 0000000..18e388c --- /dev/null +++ b/tests/test_general.py @@ -0,0 +1,67 @@ +import os +import platform +import sys +import unittest + +sys.path.insert(0, '..') +from pycparser import parse_file, c_ast + +CPPPATH = 'cpp' + + +# Test successful parsing +# +class TestParsing(unittest.TestCase): + def _find_file(self, name): + """ Find a c file by name, taking into account the current dir can be + in a couple of typical places + """ + testdir = os.path.dirname(__file__) + name = os.path.join(testdir, 'c_files', name) + assert os.path.exists(name) + return name + + def test_without_cpp(self): + ast = parse_file(self._find_file('example_c_file.c')) + self.assertIsInstance(ast, c_ast.FileAST) + + @unittest.skipUnless(platform.system() == 'Linux', + 'cpp only works on Linux') + def test_with_cpp(self): + memmgr_path = self._find_file('memmgr.c') + c_files_path = os.path.dirname(memmgr_path) + ast = parse_file(memmgr_path, use_cpp=True, + cpp_path=CPPPATH, + cpp_args='-I%s' % c_files_path) + self.assertIsInstance(ast, c_ast.FileAST) + + fake_libc = os.path.join(c_files_path, '..', '..', + 'utils', 'fake_libc_include') + ast2 = parse_file(self._find_file('year.c'), use_cpp=True, + cpp_path=CPPPATH, + cpp_args=[r'-I%s' % fake_libc]) + + self.assertIsInstance(ast2, c_ast.FileAST) + + @unittest.skipUnless(platform.system() == 'Linux', + 'cpp only works on Linux') + def test_cpp_funkydir(self): + # This test contains Windows specific path escapes + if sys.platform != 'win32': + return + + c_files_path = os.path.join('tests', 'c_files') + ast = parse_file(self._find_file('simplemain.c'), use_cpp=True, + cpp_path=CPPPATH, cpp_args='-I%s' % c_files_path) + self.assertIsInstance(ast, c_ast.FileAST) + + @unittest.skipUnless(platform.system() == 'Linux', + 'cpp only works on Linux') + def test_no_real_content_after_cpp(self): + ast = parse_file(self._find_file('empty.h'), use_cpp=True, + cpp_path=CPPPATH) + self.assertIsInstance(ast, c_ast.FileAST) + + +if __name__ == '__main__': + unittest.main() @@ -0,0 +1,6 @@ +[tox] +envlist = py27,py34,py35,py36 + +[testenv] +commands = + python tests/all_tests.py diff --git a/utils/fake_libc_include/X11/Intrinsic.h b/utils/fake_libc_include/X11/Intrinsic.h new file mode 100644 index 0000000..ab7ebb3 --- /dev/null +++ b/utils/fake_libc_include/X11/Intrinsic.h @@ -0,0 +1,4 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" +#include "_X11_fake_defines.h" +#include "_X11_fake_typedefs.h" diff --git a/utils/fake_libc_include/X11/Xlib.h b/utils/fake_libc_include/X11/Xlib.h new file mode 100644 index 0000000..ab7ebb3 --- /dev/null +++ b/utils/fake_libc_include/X11/Xlib.h @@ -0,0 +1,4 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" +#include "_X11_fake_defines.h" +#include "_X11_fake_typedefs.h" diff --git a/utils/fake_libc_include/X11/_X11_fake_defines.h b/utils/fake_libc_include/X11/_X11_fake_defines.h new file mode 100644 index 0000000..c88774b --- /dev/null +++ b/utils/fake_libc_include/X11/_X11_fake_defines.h @@ -0,0 +1,16 @@ +#ifndef _X11_FAKE_DEFINES_H +#define _X11_FAKE_DEFINES_H + +#define Atom CARD32 +#define Bool int +#define KeySym CARD32 +#define Pixmap CARD32 +#define Time CARD32 +#define _XFUNCPROTOBEGIN +#define _XFUNCPROTOEND +#define _Xconst const + +#define _X_RESTRICT_KYWD +#define Cardinal unsigned int +#define Boolean int +#endif diff --git a/utils/fake_libc_include/X11/_X11_fake_typedefs.h b/utils/fake_libc_include/X11/_X11_fake_typedefs.h new file mode 100644 index 0000000..3901142 --- /dev/null +++ b/utils/fake_libc_include/X11/_X11_fake_typedefs.h @@ -0,0 +1,38 @@ +#ifndef _X11_FAKE_TYPEDEFS_H +#define _X11_FAKE_TYPEDEFS_H + +typedef char* XPointer; +typedef unsigned char KeyCode; +typedef unsigned int CARD32; +typedef unsigned long VisualID; +typedef unsigned long XIMResetState; +typedef unsigned long XID; +typedef XID Window; +typedef XID Colormap; +typedef XID Cursor; +typedef XID Drawable; +typedef void* XtPointer; +typedef XtPointer XtRequestId; +typedef struct Display Display; +typedef struct Screen Screen; +typedef struct Status Status; +typedef struct Visual Visual; +typedef struct Widget *Widget; +typedef struct XColor XColor; +typedef struct XClassHint XClassHint; +typedef struct XEvent XEvent; +typedef struct XFontStruct XFontStruct; +typedef struct XGCValues XGCValues; +typedef struct XKeyEvent XKeyEvent; +typedef struct XKeyPressedEvent XKeyPressedEvent; +typedef struct XPoint XPoint; +typedef struct XRectangle XRectangle; +typedef struct XSelectionRequestEvent XSelectionRequestEvent; +typedef struct XWindowChanges XWindowChanges; +typedef struct _XGC _XCG; +typedef struct _XGC *GC; +typedef struct _XIC *XIC; +typedef struct _XIM *XIM; +typedef struct _XImage XImage; + +#endif diff --git a/utils/fake_libc_include/_ansi.h b/utils/fake_libc_include/_ansi.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/_ansi.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/_fake_defines.h b/utils/fake_libc_include/_fake_defines.h new file mode 100644 index 0000000..35bd4ad --- /dev/null +++ b/utils/fake_libc_include/_fake_defines.h @@ -0,0 +1,201 @@ +#ifndef _FAKE_DEFINES_H +#define _FAKE_DEFINES_H + +#define NULL 0 +#define BUFSIZ 1024 +#define FOPEN_MAX 20 +#define FILENAME_MAX 1024 + +#ifndef SEEK_SET +#define SEEK_SET 0 /* set file offset to offset */ +#endif +#ifndef SEEK_CUR +#define SEEK_CUR 1 /* set file offset to current plus offset */ +#endif +#ifndef SEEK_END +#define SEEK_END 2 /* set file offset to EOF plus offset */ +#endif + +#define __LITTLE_ENDIAN 1234 +#define LITTLE_ENDIAN __LITTLE_ENDIAN +#define __BIG_ENDIAN 4321 +#define BIG_ENDIAN __BIG_ENDIAN +#define __BYTE_ORDER __LITTLE_ENDIAN +#define BYTE_ORDER __BYTE_ORDER + +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 + +#define UCHAR_MAX 255 +#define USHRT_MAX 65535 +#define UINT_MAX 4294967295U +#define RAND_MAX 32767 +#define INT_MAX 32767 + +/* C99 inttypes.h defines */ +#define PRId8 "d" +#define PRIi8 "i" +#define PRIo8 "o" +#define PRIu8 "u" +#define PRIx8 "x" +#define PRIX8 "X" +#define PRId16 "d" +#define PRIi16 "i" +#define PRIo16 "o" +#define PRIu16 "u" +#define PRIx16 "x" +#define PRIX16 "X" +#define PRId32 "d" +#define PRIi32 "i" +#define PRIo32 "o" +#define PRIu32 "u" +#define PRIx32 "x" +#define PRIX32 "X" +#define PRId64 "d" +#define PRIi64 "i" +#define PRIo64 "o" +#define PRIu64 "u" +#define PRIx64 "x" +#define PRIX64 "X" +#define PRIdLEAST8 "d" +#define PRIiLEAST8 "i" +#define PRIoLEAST8 "o" +#define PRIuLEAST8 "u" +#define PRIxLEAST8 "x" +#define PRIXLEAST8 "X" +#define PRIdLEAST16 "d" +#define PRIiLEAST16 "i" +#define PRIoLEAST16 "o" +#define PRIuLEAST16 "u" +#define PRIxLEAST16 "x" +#define PRIXLEAST16 "X" +#define PRIdLEAST32 "d" +#define PRIiLEAST32 "i" +#define PRIoLEAST32 "o" +#define PRIuLEAST32 "u" +#define PRIxLEAST32 "x" +#define PRIXLEAST32 "X" +#define PRIdLEAST64 "d" +#define PRIiLEAST64 "i" +#define PRIoLEAST64 "o" +#define PRIuLEAST64 "u" +#define PRIxLEAST64 "x" +#define PRIXLEAST64 "X" +#define PRIdFAST8 "d" +#define PRIiFAST8 "i" +#define PRIoFAST8 "o" +#define PRIuFAST8 "u" +#define PRIxFAST8 "x" +#define PRIXFAST8 "X" +#define PRIdFAST16 "d" +#define PRIiFAST16 "i" +#define PRIoFAST16 "o" +#define PRIuFAST16 "u" +#define PRIxFAST16 "x" +#define PRIXFAST16 "X" +#define PRIdFAST32 "d" +#define PRIiFAST32 "i" +#define PRIoFAST32 "o" +#define PRIuFAST32 "u" +#define PRIxFAST32 "x" +#define PRIXFAST32 "X" +#define PRIdFAST64 "d" +#define PRIiFAST64 "i" +#define PRIoFAST64 "o" +#define PRIuFAST64 "u" +#define PRIxFAST64 "x" +#define PRIXFAST64 "X" +#define PRIdPTR "d" +#define PRIiPTR "i" +#define PRIoPTR "o" +#define PRIuPTR "u" +#define PRIxPTR "x" +#define PRIXPTR "X" +#define PRIdMAX "d" +#define PRIiMAX "i" +#define PRIoMAX "o" +#define PRIuMAX "u" +#define PRIxMAX "x" +#define PRIXMAX "X" +#define SCNd8 "d" +#define SCNi8 "i" +#define SCNo8 "o" +#define SCNu8 "u" +#define SCNx8 "x" +#define SCNd16 "d" +#define SCNi16 "i" +#define SCNo16 "o" +#define SCNu16 "u" +#define SCNx16 "x" +#define SCNd32 "d" +#define SCNi32 "i" +#define SCNo32 "o" +#define SCNu32 "u" +#define SCNx32 "x" +#define SCNd64 "d" +#define SCNi64 "i" +#define SCNo64 "o" +#define SCNu64 "u" +#define SCNx64 "x" +#define SCNdLEAST8 "d" +#define SCNiLEAST8 "i" +#define SCNoLEAST8 "o" +#define SCNuLEAST8 "u" +#define SCNxLEAST8 "x" +#define SCNdLEAST16 "d" +#define SCNiLEAST16 "i" +#define SCNoLEAST16 "o" +#define SCNuLEAST16 "u" +#define SCNxLEAST16 "x" +#define SCNdLEAST32 "d" +#define SCNiLEAST32 "i" +#define SCNoLEAST32 "o" +#define SCNuLEAST32 "u" +#define SCNxLEAST32 "x" +#define SCNdLEAST64 "d" +#define SCNiLEAST64 "i" +#define SCNoLEAST64 "o" +#define SCNuLEAST64 "u" +#define SCNxLEAST64 "x" +#define SCNdFAST8 "d" +#define SCNiFAST8 "i" +#define SCNoFAST8 "o" +#define SCNuFAST8 "u" +#define SCNxFAST8 "x" +#define SCNdFAST16 "d" +#define SCNiFAST16 "i" +#define SCNoFAST16 "o" +#define SCNuFAST16 "u" +#define SCNxFAST16 "x" +#define SCNdFAST32 "d" +#define SCNiFAST32 "i" +#define SCNoFAST32 "o" +#define SCNuFAST32 "u" +#define SCNxFAST32 "x" +#define SCNdFAST64 "d" +#define SCNiFAST64 "i" +#define SCNoFAST64 "o" +#define SCNuFAST64 "u" +#define SCNxFAST64 "x" +#define SCNdPTR "d" +#define SCNiPTR "i" +#define SCNoPTR "o" +#define SCNuPTR "u" +#define SCNxPTR "x" +#define SCNdMAX "d" +#define SCNiMAX "i" +#define SCNoMAX "o" +#define SCNuMAX "u" +#define SCNxMAX "x" + +/* C99 stdbool.h defines */ +#define __bool_true_false_are_defined 1 +#define false 0 +#define true 1 + +/* va_arg macros and type*/ +#define va_start(_ap, _type) __builtin_va_start((_ap)) +#define va_arg(_ap, _type) __builtin_va_arg((_ap)) +#define va_end(_list) + +#endif diff --git a/utils/fake_libc_include/_fake_typedefs.h b/utils/fake_libc_include/_fake_typedefs.h new file mode 100644 index 0000000..dfcc653 --- /dev/null +++ b/utils/fake_libc_include/_fake_typedefs.h @@ -0,0 +1,172 @@ +#ifndef _FAKE_TYPEDEFS_H +#define _FAKE_TYPEDEFS_H + +typedef int size_t; +typedef int __builtin_va_list; +typedef int __gnuc_va_list; +typedef int va_list; +typedef int __int8_t; +typedef int __uint8_t; +typedef int __int16_t; +typedef int __uint16_t; +typedef int __int_least16_t; +typedef int __uint_least16_t; +typedef int __int32_t; +typedef int __uint32_t; +typedef int __int64_t; +typedef int __uint64_t; +typedef int __int_least32_t; +typedef int __uint_least32_t; +typedef int __s8; +typedef int __u8; +typedef int __s16; +typedef int __u16; +typedef int __s32; +typedef int __u32; +typedef int __s64; +typedef int __u64; +typedef int _LOCK_T; +typedef int _LOCK_RECURSIVE_T; +typedef int _off_t; +typedef int __dev_t; +typedef int __uid_t; +typedef int __gid_t; +typedef int _off64_t; +typedef int _fpos_t; +typedef int _ssize_t; +typedef int wint_t; +typedef int _mbstate_t; +typedef int _flock_t; +typedef int _iconv_t; +typedef int __ULong; +typedef int __FILE; +typedef int ptrdiff_t; +typedef int wchar_t; +typedef int __off_t; +typedef int __pid_t; +typedef int __loff_t; +typedef int u_char; +typedef int u_short; +typedef int u_int; +typedef int u_long; +typedef int ushort; +typedef int uint; +typedef int clock_t; +typedef int time_t; +typedef int daddr_t; +typedef int caddr_t; +typedef int ino_t; +typedef int off_t; +typedef int dev_t; +typedef int uid_t; +typedef int gid_t; +typedef int pid_t; +typedef int key_t; +typedef int ssize_t; +typedef int mode_t; +typedef int nlink_t; +typedef int fd_mask; +typedef int _types_fd_set; +typedef int clockid_t; +typedef int timer_t; +typedef int useconds_t; +typedef int suseconds_t; +typedef int FILE; +typedef int fpos_t; +typedef int cookie_read_function_t; +typedef int cookie_write_function_t; +typedef int cookie_seek_function_t; +typedef int cookie_close_function_t; +typedef int cookie_io_functions_t; +typedef int div_t; +typedef int ldiv_t; +typedef int lldiv_t; +typedef int sigset_t; +typedef int __sigset_t; +typedef int _sig_func_ptr; +typedef int sig_atomic_t; +typedef int __tzrule_type; +typedef int __tzinfo_type; +typedef int mbstate_t; +typedef int sem_t; +typedef int pthread_t; +typedef int pthread_attr_t; +typedef int pthread_mutex_t; +typedef int pthread_mutexattr_t; +typedef int pthread_cond_t; +typedef int pthread_condattr_t; +typedef int pthread_key_t; +typedef int pthread_once_t; +typedef int pthread_rwlock_t; +typedef int pthread_rwlockattr_t; +typedef int pthread_spinlock_t; +typedef int pthread_barrier_t; +typedef int pthread_barrierattr_t; +typedef int jmp_buf; +typedef int rlim_t; +typedef int sa_family_t; +typedef int sigjmp_buf; +typedef int stack_t; +typedef int siginfo_t; +typedef int z_stream; + +/* C99 exact-width integer types */ +typedef int int8_t; +typedef int uint8_t; +typedef int int16_t; +typedef int uint16_t; +typedef int int32_t; +typedef int uint32_t; +typedef int int64_t; +typedef int uint64_t; + +/* C99 minimum-width integer types */ +typedef int int_least8_t; +typedef int uint_least8_t; +typedef int int_least16_t; +typedef int uint_least16_t; +typedef int int_least32_t; +typedef int uint_least32_t; +typedef int int_least64_t; +typedef int uint_least64_t; + +/* C99 fastest minimum-width integer types */ +typedef int int_fast8_t; +typedef int uint_fast8_t; +typedef int int_fast16_t; +typedef int uint_fast16_t; +typedef int int_fast32_t; +typedef int uint_fast32_t; +typedef int int_fast64_t; +typedef int uint_fast64_t; + +/* C99 integer types capable of holding object pointers */ +typedef int intptr_t; +typedef int uintptr_t; + +/* C99 greatest-width integer types */ +typedef int intmax_t; +typedef int uintmax_t; + +/* C99 stdbool.h bool type. _Bool is built-in in C99 */ +typedef _Bool bool; + +/* Mir typedefs */ +typedef void* MirEGLNativeWindowType; +typedef void* MirEGLNativeDisplayType; +typedef struct MirConnection MirConnection; +typedef struct MirSurface MirSurface; +typedef struct MirSurfaceSpec MirSurfaceSpec; +typedef struct MirScreencast MirScreencast; +typedef struct MirPromptSession MirPromptSession; +typedef struct MirBufferStream MirBufferStream; +typedef struct MirPersistentId MirPersistentId; +typedef struct MirBlob MirBlob; +typedef struct MirDisplayConfig MirDisplayConfig; + +/* xcb typedefs */ +typedef struct xcb_connection_t xcb_connection_t; +typedef uint32_t xcb_window_t; +typedef uint32_t xcb_visualid_t; + +#endif diff --git a/utils/fake_libc_include/_syslist.h b/utils/fake_libc_include/_syslist.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/_syslist.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/aio.h b/utils/fake_libc_include/aio.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/aio.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/alloca.h b/utils/fake_libc_include/alloca.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/alloca.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ar.h b/utils/fake_libc_include/ar.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ar.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/argz.h b/utils/fake_libc_include/argz.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/argz.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/arpa/inet.h b/utils/fake_libc_include/arpa/inet.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/arpa/inet.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/asm-generic/int-ll64.h b/utils/fake_libc_include/asm-generic/int-ll64.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/asm-generic/int-ll64.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/assert.h b/utils/fake_libc_include/assert.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/assert.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/complex.h b/utils/fake_libc_include/complex.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/complex.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/cpio.h b/utils/fake_libc_include/cpio.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/cpio.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ctype.h b/utils/fake_libc_include/ctype.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ctype.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/dirent.h b/utils/fake_libc_include/dirent.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/dirent.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/dlfcn.h b/utils/fake_libc_include/dlfcn.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/dlfcn.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/endian.h b/utils/fake_libc_include/endian.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/endian.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/envz.h b/utils/fake_libc_include/envz.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/envz.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/errno.h b/utils/fake_libc_include/errno.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/errno.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/fastmath.h b/utils/fake_libc_include/fastmath.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/fastmath.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/fcntl.h b/utils/fake_libc_include/fcntl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/fcntl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/features.h b/utils/fake_libc_include/features.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/features.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/fenv.h b/utils/fake_libc_include/fenv.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/fenv.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/float.h b/utils/fake_libc_include/float.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/float.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/fmtmsg.h b/utils/fake_libc_include/fmtmsg.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/fmtmsg.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/fnmatch.h b/utils/fake_libc_include/fnmatch.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/fnmatch.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ftw.h b/utils/fake_libc_include/ftw.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ftw.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/getopt.h b/utils/fake_libc_include/getopt.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/getopt.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/glob.h b/utils/fake_libc_include/glob.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/glob.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/grp.h b/utils/fake_libc_include/grp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/grp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/iconv.h b/utils/fake_libc_include/iconv.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/iconv.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ieeefp.h b/utils/fake_libc_include/ieeefp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ieeefp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/inttypes.h b/utils/fake_libc_include/inttypes.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/inttypes.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/iso646.h b/utils/fake_libc_include/iso646.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/iso646.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/langinfo.h b/utils/fake_libc_include/langinfo.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/langinfo.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/libgen.h b/utils/fake_libc_include/libgen.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/libgen.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/libintl.h b/utils/fake_libc_include/libintl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/libintl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/limits.h b/utils/fake_libc_include/limits.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/limits.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/linux/socket.h b/utils/fake_libc_include/linux/socket.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/linux/socket.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/linux/version.h b/utils/fake_libc_include/linux/version.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/linux/version.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/locale.h b/utils/fake_libc_include/locale.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/locale.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/malloc.h b/utils/fake_libc_include/malloc.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/malloc.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/math.h b/utils/fake_libc_include/math.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/math.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/mir_toolkit/client_types.h b/utils/fake_libc_include/mir_toolkit/client_types.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/mir_toolkit/client_types.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/monetary.h b/utils/fake_libc_include/monetary.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/monetary.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/mqueue.h b/utils/fake_libc_include/mqueue.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/mqueue.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ndbm.h b/utils/fake_libc_include/ndbm.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ndbm.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/net/if.h b/utils/fake_libc_include/net/if.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/net/if.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/netdb.h b/utils/fake_libc_include/netdb.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/netdb.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/netinet/in.h b/utils/fake_libc_include/netinet/in.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/netinet/in.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/netinet/tcp.h b/utils/fake_libc_include/netinet/tcp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/netinet/tcp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/newlib.h b/utils/fake_libc_include/newlib.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/newlib.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/nl_types.h b/utils/fake_libc_include/nl_types.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/nl_types.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/openssl/err.h b/utils/fake_libc_include/openssl/err.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/openssl/err.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/openssl/evp.h b/utils/fake_libc_include/openssl/evp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/openssl/evp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/openssl/hmac.h b/utils/fake_libc_include/openssl/hmac.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/openssl/hmac.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/openssl/ssl.h b/utils/fake_libc_include/openssl/ssl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/openssl/ssl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/openssl/x509v3.h b/utils/fake_libc_include/openssl/x509v3.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/openssl/x509v3.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/paths.h b/utils/fake_libc_include/paths.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/paths.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/poll.h b/utils/fake_libc_include/poll.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/poll.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/process.h b/utils/fake_libc_include/process.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/process.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/pthread.h b/utils/fake_libc_include/pthread.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/pthread.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/pwd.h b/utils/fake_libc_include/pwd.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/pwd.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/reent.h b/utils/fake_libc_include/reent.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/reent.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/regdef.h b/utils/fake_libc_include/regdef.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/regdef.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/regex.h b/utils/fake_libc_include/regex.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/regex.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sched.h b/utils/fake_libc_include/sched.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sched.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/search.h b/utils/fake_libc_include/search.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/search.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/semaphore.h b/utils/fake_libc_include/semaphore.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/semaphore.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/setjmp.h b/utils/fake_libc_include/setjmp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/setjmp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/signal.h b/utils/fake_libc_include/signal.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/signal.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/spawn.h b/utils/fake_libc_include/spawn.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/spawn.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stdarg.h b/utils/fake_libc_include/stdarg.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stdarg.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stdbool.h b/utils/fake_libc_include/stdbool.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stdbool.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stddef.h b/utils/fake_libc_include/stddef.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stddef.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stdint.h b/utils/fake_libc_include/stdint.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stdint.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stdio.h b/utils/fake_libc_include/stdio.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stdio.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stdlib.h b/utils/fake_libc_include/stdlib.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stdlib.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/string.h b/utils/fake_libc_include/string.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/string.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/strings.h b/utils/fake_libc_include/strings.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/strings.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/stropts.h b/utils/fake_libc_include/stropts.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/stropts.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/ioctl.h b/utils/fake_libc_include/sys/ioctl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/ioctl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/ipc.h b/utils/fake_libc_include/sys/ipc.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/ipc.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/mman.h b/utils/fake_libc_include/sys/mman.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/mman.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/msg.h b/utils/fake_libc_include/sys/msg.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/msg.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/poll.h b/utils/fake_libc_include/sys/poll.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/poll.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/resource.h b/utils/fake_libc_include/sys/resource.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/resource.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/select.h b/utils/fake_libc_include/sys/select.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/select.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/sem.h b/utils/fake_libc_include/sys/sem.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/sem.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/shm.h b/utils/fake_libc_include/sys/shm.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/shm.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/socket.h b/utils/fake_libc_include/sys/socket.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/socket.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/stat.h b/utils/fake_libc_include/sys/stat.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/stat.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/statvfs.h b/utils/fake_libc_include/sys/statvfs.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/statvfs.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/sysctl.h b/utils/fake_libc_include/sys/sysctl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/sysctl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/time.h b/utils/fake_libc_include/sys/time.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/time.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/times.h b/utils/fake_libc_include/sys/times.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/times.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/types.h b/utils/fake_libc_include/sys/types.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/types.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/uio.h b/utils/fake_libc_include/sys/uio.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/uio.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/un.h b/utils/fake_libc_include/sys/un.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/un.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/utsname.h b/utils/fake_libc_include/sys/utsname.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/utsname.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/sys/wait.h b/utils/fake_libc_include/sys/wait.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/sys/wait.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/syslog.h b/utils/fake_libc_include/syslog.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/syslog.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/tar.h b/utils/fake_libc_include/tar.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/tar.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/termios.h b/utils/fake_libc_include/termios.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/termios.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/tgmath.h b/utils/fake_libc_include/tgmath.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/tgmath.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/time.h b/utils/fake_libc_include/time.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/time.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/trace.h b/utils/fake_libc_include/trace.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/trace.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/ulimit.h b/utils/fake_libc_include/ulimit.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/ulimit.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/unctrl.h b/utils/fake_libc_include/unctrl.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/unctrl.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/unistd.h b/utils/fake_libc_include/unistd.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/unistd.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/utime.h b/utils/fake_libc_include/utime.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/utime.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/utmp.h b/utils/fake_libc_include/utmp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/utmp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/utmpx.h b/utils/fake_libc_include/utmpx.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/utmpx.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/wchar.h b/utils/fake_libc_include/wchar.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/wchar.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/wctype.h b/utils/fake_libc_include/wctype.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/wctype.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/wordexp.h b/utils/fake_libc_include/wordexp.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/wordexp.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/xcb/xcb.h b/utils/fake_libc_include/xcb/xcb.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/xcb/xcb.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/fake_libc_include/zlib.h b/utils/fake_libc_include/zlib.h new file mode 100644 index 0000000..f952c1d --- /dev/null +++ b/utils/fake_libc_include/zlib.h @@ -0,0 +1,2 @@ +#include "_fake_defines.h" +#include "_fake_typedefs.h" diff --git a/utils/internal/constptr.c b/utils/internal/constptr.c new file mode 100644 index 0000000..2fe14bf --- /dev/null +++ b/utils/internal/constptr.c @@ -0,0 +1,9 @@ +void foo(char * const * arg) { + arg += 1; + (*arg) += 1; +} + +void foo2(char ** const arg) { + arg += 1; + (*arg) += 1; +} diff --git a/utils/internal/cppify.bat b/utils/internal/cppify.bat new file mode 100644 index 0000000..af69f5f --- /dev/null +++ b/utils/internal/cppify.bat @@ -0,0 +1,3 @@ +REM ~ ..\cpp -D__i386__ -I"D:\eli\cpp_stuff\libc_include" -D__extension__ example_c_file.c > example_c_file_pp.c +REM ~ ..\cpp -D__i386__ -I"D:\eli\c_analyzing\pycparser-trunk\utils\fake_libc_include" example_c_file.c > example_c_file_pp.c +..\cpp -D__i386__ -I"D:\eli\c_analyzing\pycparser-trunk\utils\fake_libc_include" zc.c > zc_pp.c diff --git a/utils/internal/example_c_file.c b/utils/internal/example_c_file.c new file mode 100644 index 0000000..35da01d --- /dev/null +++ b/utils/internal/example_c_file.c @@ -0,0 +1,25 @@ +/* a comment / */ +/* "not a string" */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <ctype.h> +#include <signal.h> +#include <time.h> +#include <wchar.h> + +/* + multiline comment + comment +*/ + +int main() +{ + auto char* multi = "a multi"; /* and a comment !*/ +} + +/* A final comment for good measure /* /* /* */ + + + diff --git a/utils/internal/fake_includes.py b/utils/internal/fake_includes.py new file mode 100644 index 0000000..1ce69fa --- /dev/null +++ b/utils/internal/fake_includes.py @@ -0,0 +1,13 @@ +import os.path + +for cur_path, dirs, files in os.walk('.'): + if cur_path == '.': + for f in files: + if f.endswith('.h'): + print f + fo = open(f, 'w') + fo.write('#include "_fake_defines.h"\n') + fo.write('#include "_fake_typedefs.h"\n') + fo.close() + + diff --git a/utils/internal/make_fake_typedefs.py b/utils/internal/make_fake_typedefs.py new file mode 100644 index 0000000..b82e92f --- /dev/null +++ b/utils/internal/make_fake_typedefs.py @@ -0,0 +1,21 @@ +import sys +sys.path.insert(0, '../..') + +from pycparser import c_parser, c_ast, parse_file + + +class MyVisitor(c_ast.NodeVisitor): + def visit_Typedef(self, node): + print 'typedef int %s;' % node.name + + + +def generate_fake_typedefs(filename): + ast = parse_file(filename, use_cpp=True, cpp_path="../cpp.exe") + v = MyVisitor() + v.visit(ast) + + +if __name__ == "__main__": + generate_fake_typedefs('example_c_file_pp.c') + diff --git a/utils/internal/memprofiling.py b/utils/internal/memprofiling.py new file mode 100644 index 0000000..5b25120 --- /dev/null +++ b/utils/internal/memprofiling.py @@ -0,0 +1,121 @@ +import sys +from pycparser import parse_file +from pycparser.c_ast import * +from pycparser.c_parser import CParser, Coord, ParseError +from pycparser.c_lexer import CLexer + + +def expand_decl(decl): + """ Converts the declaration into a nested list. + """ + typ = type(decl) + + if typ == TypeDecl: + return ['TypeDecl', expand_decl(decl.type)] + elif typ == IdentifierType: + return ['IdentifierType', decl.names] + elif typ == ID: + return ['ID', decl.name] + elif typ in [Struct, Union]: + decls = [expand_decl(d) for d in decl.decls or []] + return [typ.__name__, decl.name, decls] + else: + nested = expand_decl(decl.type) + + if typ == Decl: + if decl.quals: + return ['Decl', decl.quals, decl.name, nested] + else: + return ['Decl', decl.name, nested] + elif typ == Typename: # for function parameters + if decl.quals: + return ['Typename', decl.quals, nested] + else: + return ['Typename', nested] + elif typ == ArrayDecl: + dimval = decl.dim.value if decl.dim else '' + return ['ArrayDecl', dimval, nested] + elif typ == PtrDecl: + return ['PtrDecl', nested] + elif typ == Typedef: + return ['Typedef', decl.name, nested] + elif typ == FuncDecl: + if decl.args: + params = [expand_decl(param) for param in decl.args.params] + else: + params = [] + return ['FuncDecl', params, nested] + +#----------------------------------------------------------------- +class NodeVisitor(object): + def __init__(self): + self.current_parent = None + + def visit(self, node): + """ Visit a node. + """ + method = 'visit_' + node.__class__.__name__ + visitor = getattr(self, method, self.generic_visit) + return visitor(node) + + def visit_FuncCall(self, node): + print("Visiting FuncCall") + print(node.show()) + print('---- parent ----') + print(self.current_parent.show()) + + def generic_visit(self, node): + """ Called if no explicit visitor function exists for a + node. Implements preorder visiting of the node. + """ + oldparent = self.current_parent + self.current_parent = node + for c in node.children(): + self.visit(c) + self.current_parent = oldparent + + +def heapyprofile(): + # pip install guppy + # [works on python 2.7, AFAIK] + from guppy import hpy + import gc + + hp = hpy() + ast = parse_file('/tmp/197.c') + gc.collect() + h = hp.heap() + print(h) + + +def memprofile(): + import resource + import tracemalloc + + tracemalloc.start() + + ast = parse_file('/tmp/197.c') + + print('Memory usage: %s (kb)' % + resource.getrusage(resource.RUSAGE_SELF).ru_maxrss) + + snapshot = tracemalloc.take_snapshot() + print("[ tracemalloc stats ]") + for stat in snapshot.statistics('lineno')[:20]: + print(stat) + + +if __name__ == "__main__": + source_code = r'''void foo() { + L"hi" L"there"; +} + ''' + + memprofile() + #heapyprofile() + + #parser = CParser() + #ast = parser.parse(source_code, filename='zz') + #ast.show(showcoord=True, attrnames=True, nodenames=True) + + diff --git a/utils/internal/zc.c b/utils/internal/zc.c new file mode 100644 index 0000000..5e56974 --- /dev/null +++ b/utils/internal/zc.c @@ -0,0 +1,107 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <getopt.h> + +#define PACKAGE "wgram" +#define VERSION "0.0.4" +#define MAXLINE 1024 +#define MAXGRAM 32 + +/* status epilepticus .. print help */ +void print_help(int exval); + +int main (int argc, char *argv[]) { + /* word delimeter for strtok() */ + char delim[] = ".,:;`/\"+-_(){}[]<>*&^%$#@!?~/|\\=1234567890 \t\n"; + char line[MAXLINE]; /* input buff, fgets() */ + char *stray = NULL; /* returned value by strtok() */ + char **strarray = NULL; /* array to hold all entrys */ + int i = 0; /* general counter */ + int strcount = 0; /* number of entrys in pointer array */ + int N = 3, pos = 0; /* ngram size, 3 in this case */ + int opt = 0; /* holds command line opt nr.. */ + int word_flag = 0; /* print only the `raw' words */ + FILE *fp = stdin; /* read input from `FILE', default is stdin */ + + while((opt = getopt(argc, argv, "hvn:wf:")) != -1) { + switch(opt) { + case 'h': + print_help(0); + break; + case 'v': + exit(0); + break; + case 'n': + N = atoi(optarg); + if(N > MAXGRAM || N < 2) { + fprintf(stderr, "%s: Error - Ngram length `%d' out of range `0-%d'\n", + PACKAGE, N, MAXGRAM); + return 1; + } + break; + case 'w': + word_flag = 1; + break; + case 'f': + if(freopen(optarg, "r", fp) == NULL) { + fprintf(stderr, "%s: Error - opening `%s'\n", PACKAGE, optarg); + return 1; + } + break; + case '?': + fprintf(stderr, "%s: Error - No such option: `%c'\n\n", PACKAGE, optopt); + print_help(1); + } /* switch */ + } /* while */ + + /* start reading lines from file pointer, add all entrys to **strarray */ + while((fgets(line, MAXLINE, fp)) != NULL) { + if(strlen(line) < 2) + continue; + + stray = strtok(line, delim); + while(stray != NULL) { + strarray = (char **)realloc(strarray, (strcount + 1) * sizeof(char *)); + strarray[strcount++] = strdup(stray); + stray = strtok(NULL, delim); + } + } + + if(word_flag == 0) { + /* + // print the array of strings, jumping back each time + // (N - 1) positions if a whole ngram of words has been printed + */ + for(i = 0, pos = N; i < strcount; i++, pos--) { + if(pos == 0) pos = N, i -= (N - 1), printf("\n"); + printf("%s ", strarray[i]); + } + printf("\n"); + } else { + /* print raw words */ + for(i = 0; i < strcount; i++) + printf("%s\n", strarray[i]); + } + + /* free the string array */ + for(i = 0; i < strcount; i++) + free(strarray[i]); + + free(strarray); + return 0; +} + +/* status epilepticus .. print help */ +void print_help(int exval) { + printf("%s,%s extract N-grams from text data\n", PACKAGE, VERSION); + printf("Usage: %s [-h] [-v] [-n INT] [-w] [-f FILE]\n\n", PACKAGE); + + printf(" -h print this help and exit\n"); + printf(" -v print version and exit\n\n"); + + printf(" -n INT set ngram length (default=3)\n"); + printf(" -w print only the extracted words\n"); + printf(" -f FILE read input from `FILE' (default=stdin)\n\n"); + exit(exval); +} diff --git a/utils/internal/zz_parse.py b/utils/internal/zz_parse.py new file mode 100644 index 0000000..39978d1 --- /dev/null +++ b/utils/internal/zz_parse.py @@ -0,0 +1,21 @@ +from __future__ import print_function + +import sys +from pycparser import c_parser, c_generator, c_ast, parse_file + + +if __name__ == "__main__": + parser = c_parser.CParser() + code = r''' + void* ptr = (int[ ]){0}; + ''' + + print(code) + ast = parser.parse(code) + ast.show(attrnames=True, nodenames=True) + print(ast.ext[0].__slots__) + print(dir(ast.ext[0])) + + print("==== From C generator:") + generator = c_generator.CGenerator() + print(generator.visit(ast)) |