├── pycparserext ├── __init__.py ├── ext_c_lexer.py ├── ext_c_generator.py └── ext_c_parser.py ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── autopush.yml │ └── ci.yml ├── .gitlab-ci.yml ├── .editorconfig ├── README.rst ├── LICENSE ├── pyproject.toml └── test └── test_pycparserext.py /pycparserext/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .*.sw[po] 3 | .sw[po] 4 | *~ 5 | *.pyc 6 | *.pyo 7 | *.egg-info 8 | MANIFEST 9 | dist 10 | setuptools*egg 11 | setuptools.pth 12 | distribute*egg 13 | distribute*tar.gz 14 | lextab.py 15 | yacctab.py 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Set update schedule for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | 9 | # vim: sw=4 10 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | Python 3: 2 | script: 3 | - py_version=3 4 | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh 5 | - ". ./build-and-test-py-project.sh" 6 | tags: 7 | - python3 8 | except: 9 | - tags 10 | artifacts: 11 | reports: 12 | junit: test/pytest.xml 13 | 14 | Ruff: 15 | script: 16 | - pipx install ruff 17 | - ruff check 18 | tags: 19 | - docker-runner 20 | except: 21 | - tags 22 | -------------------------------------------------------------------------------- /.github/workflows/autopush.yml: -------------------------------------------------------------------------------- 1 | name: Gitlab mirror 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | autopush: 9 | name: Automatic push to gitlab.tiker.net 10 | if: startsWith(github.repository, 'inducer/') 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v6 14 | - run: | 15 | curl -L -O https://tiker.net/ci-support-v0 16 | . ./ci-support-v0 17 | mirror_github_to_gitlab 18 | 19 | env: 20 | GITLAB_AUTOPUSH_KEY: ${{ secrets.GITLAB_AUTOPUSH_KEY }} 21 | 22 | # vim: sw=4 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | # https://github.com/editorconfig/editorconfig-vim 3 | # https://github.com/editorconfig/editorconfig-emacs 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.py] 15 | indent_size = 4 16 | 17 | [*.rst] 18 | indent_size = 4 19 | 20 | [*.cpp] 21 | indent_size = 2 22 | 23 | [*.hpp] 24 | indent_size = 2 25 | 26 | # There may be one in doc/ 27 | [Makefile] 28 | indent_style = tab 29 | 30 | # https://github.com/microsoft/vscode/issues/1679 31 | [*.md] 32 | trim_trailing_whitespace = false 33 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Extended functionality for pycparser 2 | ==================================== 3 | 4 | .. image:: https://gitlab.tiker.net/inducer/pycparserext/badges/main/pipeline.svg 5 | :alt: Gitlab Build Status 6 | :target: https://gitlab.tiker.net/inducer/pycparserext/commits/main 7 | .. image:: https://github.com/inducer/pycparserext/workflows/CI/badge.svg?branch=main&event=push 8 | :alt: Github Build Status 9 | :target: https://github.com/inducer/pycparserext/actions?query=branch%3Amain+workflow%3ACI+event%3Apush 10 | .. image:: https://badge.fury.io/py/pycparserext.png 11 | :alt: Python Package Index Release Page 12 | :target: https://pypi.org/project/pycparserext/ 13 | 14 | Extended functionality for Eli Bendersky's 15 | `pycparser `_, 16 | in particular support for parsing GNU extensions and 17 | OpenCL. 18 | 19 | See also the `github code repository `_. 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | paths-ignore: 8 | - 'doc/*.rst' 9 | schedule: 10 | - cron: '17 3 * * 0' 11 | 12 | jobs: 13 | ruff: 14 | name: Ruff 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v6 18 | - uses: actions/setup-python@v6 19 | - name: "Main Script" 20 | run: | 21 | pip install ruff 22 | ruff check 23 | 24 | pytest: 25 | name: Pytest on Py${{ matrix.python-version }} 26 | runs-on: ubuntu-latest 27 | strategy: 28 | matrix: 29 | python-version: ['3.8', '3.9', '3.x'] 30 | steps: 31 | - uses: actions/checkout@v6 32 | - 33 | uses: actions/setup-python@v6 34 | with: 35 | python-version: ${{ matrix.python-version }} 36 | - name: "Main Script" 37 | run: | 38 | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh 39 | . ./build-and-test-py-project.sh 40 | 41 | # vim: sw=4 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | pycparserext is licensed to you under the MIT/X Consortium license: 2 | 3 | Copyright (c) 2009-18 Andreas Klöckner and Contributors. 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "pycparserext" 7 | version = "2024.1" 8 | description = "Extensions for pycparser" 9 | readme = "README.rst" 10 | license = "MIT" 11 | requires-python = "~=3.8" 12 | authors = [ 13 | { name = "Andreas Kloeckner", email = "inform@tiker.net" }, 14 | ] 15 | classifiers = [ 16 | "Development Status :: 4 - Beta", 17 | "Intended Audience :: Developers", 18 | "Intended Audience :: Other Audience", 19 | "Intended Audience :: Science/Research", 20 | "License :: OSI Approved :: MIT License", 21 | "Natural Language :: English", 22 | "Programming Language :: Python", 23 | "Topic :: Utilities", 24 | ] 25 | dependencies = [ 26 | "ply>=3.4", 27 | "pycparser~=2.21", 28 | ] 29 | 30 | [project.urls] 31 | Homepage = "https://github.com/inducer/pycparserext" 32 | 33 | [tool.hatch.build.targets.sdist] 34 | include = [ 35 | "/pycparserext", 36 | ] 37 | [tool.ruff] 38 | preview = true 39 | 40 | [tool.ruff.lint] 41 | extend-select = [ 42 | "B", # flake8-bugbear 43 | "C", # flake8-comprehensions 44 | "E", # pycodestyle 45 | "F", # pyflakes 46 | "G", # flake8-logging-format 47 | "I", # flake8-isort 48 | "N", # pep8-naming 49 | "NPY", # numpy 50 | "Q", # flake8-quotes 51 | "UP", # pyupgrade 52 | "RUF", # ruff 53 | "W", # pycodestyle 54 | ] 55 | extend-ignore = [ 56 | "C90", # McCabe complexity 57 | "E221", # multiple spaces before operator 58 | "E226", # missing whitespace around arithmetic operator 59 | "E402", # module-level import not at top of file 60 | "UP006", # updated annotations due to __future__ import 61 | "UP007", # updated annotations due to __future__ import 62 | "UP031", # use f-strings instead of % 63 | "UP032", # use f-strings instead of .format 64 | ] 65 | [tool.ruff.lint.flake8-quotes] 66 | docstring-quotes = "double" 67 | inline-quotes = "double" 68 | multiline-quotes = "double" 69 | 70 | [tool.ruff.lint.pep8-naming] 71 | extend-ignore-names = ["visit_*", "t_*"] 72 | 73 | 74 | [tool.ruff.lint.isort] 75 | combine-as-imports = true 76 | 77 | known-first-party = [ 78 | "pycparser", 79 | ] 80 | known-local-folder = [ 81 | "pycparserext", 82 | ] 83 | lines-after-imports = 2 84 | -------------------------------------------------------------------------------- /pycparserext/ext_c_lexer.py: -------------------------------------------------------------------------------- 1 | from pycparser.c_lexer import CLexer as CLexerBase 2 | 3 | 4 | try: 5 | from pycparser.ply.lex import TOKEN 6 | except ImportError: 7 | from ply.lex import TOKEN 8 | 9 | 10 | class GnuCLexer(CLexerBase): 11 | # support '3i' for imaginary literal 12 | floating_constant = ( 13 | "((((" 14 | + CLexerBase.fractional_constant+")" 15 | + CLexerBase.exponent_part+"?)|([0-9]+" 16 | + CLexerBase.exponent_part+"))i?[FfLl]?)") 17 | 18 | @TOKEN(floating_constant) 19 | def t_FLOAT_CONST(self, t): 20 | return t 21 | 22 | t_pppragma_ignore = " \t<>.-{}();+-*/$%@&^~!?:,0123456789=" 23 | 24 | 25 | class GNUCLexer(GnuCLexer): 26 | def __init__(self, *args, **kwargs): 27 | from warnings import warn 28 | warn("GNUCLexer is now called GnuCLexer", 29 | DeprecationWarning, stacklevel=2) 30 | 31 | GnuCLexer.__init__(self, *args, **kwargs) 32 | 33 | 34 | class OpenCLCLexer(CLexerBase): 35 | tokens = (*CLexerBase.tokens, "LINECOMMENT") 36 | states = ( 37 | # ('comment', 'exclusive'), 38 | # ('preproc', 'exclusive'), 39 | ("ppline", "exclusive"), # unused 40 | ("pppragma", "exclusive"), # unused 41 | ) 42 | 43 | def t_LINECOMMENT(self, t): 44 | r"\/\/([^\n]+)\n" 45 | t.lexer.lineno += t.value.count("\n") 46 | 47 | # overrides pycparser, must have same name 48 | def t_PPHASH(self, t): 49 | r"[ \t]*\#([^\n]|\\\n)+[^\n\\]\n" 50 | t.lexer.lineno += t.value.count("\n") 51 | return t 52 | 53 | 54 | def add_lexer_keywords(cls, keywords): 55 | cls.keywords = cls.keywords + tuple( 56 | kw.upper() for kw in keywords) 57 | 58 | cls.keyword_map = cls.keyword_map.copy() 59 | cls.keyword_map.update({kw: kw.upper() for kw in keywords}) 60 | 61 | cls.tokens = cls.tokens + tuple( 62 | kw.upper() for kw in keywords) 63 | 64 | 65 | _COMMON_KEYWORDS = [ 66 | "__attribute__", "__attribute", 67 | "__asm__", "__asm", "asm"] 68 | 69 | _GNU_KEYWORDS = [ 70 | "__typeof__", "typeof", "__typeof", 71 | "__real__", "__imag__", 72 | "__builtin_types_compatible_p", 73 | "__const", 74 | "__restrict__", "__restrict", 75 | "__inline__", "__inline", 76 | "__extension__", 77 | "__volatile", "__volatile__", 78 | "__alignof__"] 79 | 80 | add_lexer_keywords(GnuCLexer, _COMMON_KEYWORDS + _GNU_KEYWORDS) 81 | 82 | # These will be added as unadorned keywords and keywords with '__' prepended 83 | _CL_BASE_KEYWORDS = [ 84 | "kernel", "constant", "global", "local", "private", 85 | "read_only", "write_only", "read_write"] 86 | 87 | _CL_KEYWORDS = _COMMON_KEYWORDS 88 | _CL_KEYWORDS += _CL_BASE_KEYWORDS + ["__"+kw for kw in _CL_BASE_KEYWORDS] 89 | 90 | add_lexer_keywords(OpenCLCLexer, _CL_KEYWORDS) 91 | 92 | # vim: fdm=marker 93 | -------------------------------------------------------------------------------- /pycparserext/ext_c_generator.py: -------------------------------------------------------------------------------- 1 | import pycparser.c_ast as c_ast 2 | from pycparser.c_generator import CGenerator as CGeneratorBaseBuggy 3 | 4 | from pycparserext.ext_c_parser import FuncDeclExt, TypeDeclExt 5 | 6 | 7 | class CGeneratorBase(CGeneratorBaseBuggy): 8 | # bug fix 9 | def visit_UnaryOp(self, n): 10 | operand = self._parenthesize_unless_simple(n.expr) 11 | if n.op == "p++": 12 | return "%s++" % operand 13 | elif n.op == "p--": 14 | return "%s--" % operand 15 | elif n.op == "sizeof": 16 | # Always parenthesize the argument of sizeof since it can be 17 | # a name. 18 | return "sizeof(%s)" % self.visit(n.expr) 19 | elif n.op == "__alignof__": 20 | return "__alignof__(%s)" % self.visit(n.expr) 21 | else: 22 | # avoid merging of "- - x" or "__real__varname" 23 | return "%s %s" % (n.op, operand) 24 | 25 | 26 | class AsmAndAttributesMixin: 27 | def visit_Asm(self, n): 28 | components = [ 29 | n.template, 30 | ] 31 | if (n.output_operands is not None 32 | or n.input_operands is not None 33 | or n.clobbered_regs is not None): 34 | components.extend([ 35 | n.output_operands, 36 | n.input_operands, 37 | n.clobbered_regs, 38 | ]) 39 | 40 | return " %s(%s)" % ( 41 | n.asm_keyword, 42 | " : ".join( 43 | self.visit(c) for c in components)) 44 | 45 | def _generate_type(self, n, modifiers=None, emit_declname=True): 46 | """ Recursive generation from a type node. n is the type node. 47 | modifiers collects the PtrDecl, ArrayDecl and FuncDecl modifiers 48 | encountered on the way down to a TypeDecl, to allow proper 49 | generation from it. 50 | """ 51 | if modifiers is None: 52 | modifiers = [] 53 | typ = type(n) 54 | # print(n, modifiers) 55 | 56 | if typ in (c_ast.TypeDecl, TypeDeclExt): 57 | s = "" 58 | if n.quals: 59 | s += " ".join(n.quals) + " " 60 | s += self.visit(n.type) 61 | 62 | nstr = n.declname if n.declname and emit_declname else "" 63 | # Resolve modifiers. 64 | # Wrap in parens to distinguish pointer to array and pointer to 65 | # function syntax. 66 | # 67 | for i, modifier in enumerate(modifiers): 68 | if isinstance(modifier, c_ast.ArrayDecl): 69 | if i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl): 70 | nstr = "(" + nstr + ")" 71 | 72 | # BUG FIX: pycparser ignores quals 73 | dim_quals = (" ".join(modifier.dim_quals) + " " 74 | if modifier.dim_quals else "") 75 | 76 | nstr += "[" + dim_quals + self.visit(modifier.dim) + "]" 77 | 78 | elif isinstance(modifier, c_ast.FuncDecl): 79 | if i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl): 80 | nstr = "(" + nstr + ")" 81 | nstr += "(" + self.visit(modifier.args) + ")" 82 | 83 | elif isinstance(modifier, FuncDeclExt): 84 | if i != 0 and isinstance(modifiers[i - 1], c_ast.PtrDecl): 85 | nstr = "(" + nstr + ")" 86 | nstr += "(" + self.visit(modifier.args) + ")" 87 | 88 | if modifier.asm is not None: 89 | nstr += " " + self.visit(modifier.asm) 90 | 91 | if modifier.attributes.exprs: 92 | nstr += ( 93 | " __attribute__((" 94 | + self.visit(modifier.attributes) 95 | + "))") 96 | 97 | elif isinstance(modifier, c_ast.PtrDecl): 98 | # BUG FIX: pycparser ignores quals 99 | quals = " ".join(modifier.quals) 100 | if quals: 101 | quals = quals + " " 102 | nstr = "*" + quals + nstr 103 | 104 | if hasattr(n, "asm") and n.asm: 105 | nstr += self.visit(n.asm) 106 | 107 | if hasattr(n, "attributes") and n.attributes.exprs: 108 | nstr += " __attribute__((" + self.visit(n.attributes) + "))" 109 | 110 | if nstr: 111 | s += " " + nstr 112 | return s 113 | 114 | elif typ == c_ast.Decl: 115 | return self._generate_decl(n.type) 116 | 117 | elif typ == c_ast.Typename: 118 | return self._generate_type(n.type, emit_declname=emit_declname) 119 | 120 | elif typ == c_ast.IdentifierType: 121 | return " ".join(n.names) + " " 122 | 123 | elif typ in (c_ast.ArrayDecl, c_ast.PtrDecl, c_ast.FuncDecl, FuncDeclExt): 124 | return self._generate_type( 125 | n.type, [*modifiers, n], emit_declname=emit_declname) 126 | 127 | else: 128 | return self.visit(n) 129 | 130 | def visit_AttributeSpecifier(self, n): 131 | return "__attribute__((" + self.visit(n.exprlist) + "))" 132 | 133 | 134 | class GnuCGenerator(AsmAndAttributesMixin, CGeneratorBase): 135 | def _generate_decl(self, n): 136 | """ Generation from a Decl node. 137 | """ 138 | s = "" 139 | 140 | def funcspec_to_str(i): 141 | if isinstance(i, c_ast.Node): 142 | return self.visit(i) 143 | else: 144 | return i 145 | 146 | if n.funcspec: 147 | s = " ".join(funcspec_to_str(i) for i in n.funcspec) + " " 148 | if n.storage: 149 | s += " ".join(n.storage) + " " 150 | s += self._generate_type(n.type) 151 | return s 152 | 153 | def visit_TypeOfDeclaration(self, n): 154 | return "%s(%s)" % (n.typeof_keyword, self.visit(n.declaration)) 155 | 156 | def visit_TypeOfExpression(self, n): 157 | return "%s(%s)" % (n.typeof_keyword, self.visit(n.expr)) 158 | 159 | def visit_TypeList(self, n): 160 | return ", ".join(self.visit(ch) for ch in n.types) 161 | 162 | def visit_RangeExpression(self, n): 163 | return "%s ... %s" % (self.visit(n.first), self.visit(n.last)) 164 | 165 | def visit_Struct(self, n): 166 | """Generate code for struct, handling attributes if present.""" 167 | s = self._generate_struct_union_enum(n, "struct") 168 | # If this is a StructExt with attributes, add them 169 | if hasattr(n, "attrib") and n.attrib: 170 | s += " " + self.visit(n.attrib) 171 | return s 172 | 173 | def visit_StructExt(self, n): 174 | """Generate code for StructExt with attributes.""" 175 | return self.visit_Struct(n) 176 | 177 | 178 | class GNUCGenerator(GnuCGenerator): 179 | def __init__(self): 180 | from warnings import warn 181 | warn("GNUCGenerator is now called GnuCGenerator", 182 | DeprecationWarning, stacklevel=2) 183 | 184 | 185 | class OpenCLCGenerator(AsmAndAttributesMixin, CGeneratorBase): 186 | def visit_FileAST(self, n): 187 | s = "" 188 | from pycparserext.ext_c_parser import PreprocessorLine 189 | for ext in n.ext: 190 | if isinstance(ext, (c_ast.FuncDef, PreprocessorLine)): 191 | s += self.visit(ext) 192 | else: 193 | s += self.visit(ext) + ";\n" 194 | return s 195 | 196 | def visit_PreprocessorLine(self, n): 197 | return n.contents 198 | 199 | # vim: fdm=marker 200 | -------------------------------------------------------------------------------- /test/test_pycparserext.py: -------------------------------------------------------------------------------- 1 | 2 | import pytest 3 | 4 | 5 | # Inspired from pycparser's compare_asts test function 6 | def _compare_asts(first, second): 7 | if type(first) is not type(second): 8 | return False 9 | 10 | if isinstance(first, tuple): 11 | if first[0] != second[0]: 12 | return False 13 | 14 | return _compare_asts(first[1], second[1]) 15 | 16 | for attr in first.attr_names: 17 | if getattr(first, attr) != getattr(second, attr): 18 | return False 19 | 20 | for i, c1 in enumerate(first.children()): 21 | if not _compare_asts(c1, second.children()[i]): 22 | return False 23 | return True 24 | 25 | 26 | def _round_trip_matches(src): 27 | from pycparserext.ext_c_generator import GnuCGenerator 28 | from pycparserext.ext_c_parser import GnuCParser 29 | 30 | p = GnuCParser() 31 | 32 | first_ast = p.parse(src) 33 | 34 | gen = GnuCGenerator().visit(first_ast) 35 | 36 | second_ast = p.parse(gen) 37 | 38 | if not _compare_asts(first_ast, second_ast): 39 | print("First AST:") 40 | first_ast.show() 41 | 42 | print("Generated code:") 43 | print(gen) 44 | 45 | print("Second AST:") 46 | second_ast.show() 47 | 48 | return False 49 | return True 50 | 51 | 52 | def test_asm_volatile_1(): 53 | src = """ 54 | void read_tsc(void) { 55 | long val; 56 | asm("rdtsc" : "=A" (val)); 57 | } """ 58 | from pycparserext.ext_c_parser import GnuCParser 59 | p = GnuCParser() 60 | ast = p.parse(src) 61 | ast.show() 62 | 63 | from pycparserext.ext_c_generator import GnuCGenerator 64 | print(GnuCGenerator().visit(ast)) 65 | 66 | 67 | def test_asm_volatile_2(): 68 | src = """ 69 | void read_tsc(void) { 70 | long val; 71 | asm volatile("rdtsc" : "=A" (val)); 72 | } """ 73 | from pycparserext.ext_c_parser import GnuCParser 74 | p = GnuCParser() 75 | ast = p.parse(src) 76 | ast.show() 77 | 78 | from pycparserext.ext_c_generator import GnuCGenerator 79 | print(GnuCGenerator().visit(ast)) 80 | 81 | 82 | def test_asm_volatile_3(): 83 | src = """ 84 | void read_tsc(void) { 85 | long fpenv; 86 | asm("mtfsf 255,%0" :: "f" (fpenv)); 87 | } """ 88 | from pycparserext.ext_c_parser import GnuCParser 89 | p = GnuCParser() 90 | ast = p.parse(src) 91 | ast.show() 92 | 93 | from pycparserext.ext_c_generator import GnuCGenerator 94 | print(GnuCGenerator().visit(ast)) 95 | 96 | 97 | def test_asm_volatile_4(): 98 | src = """ 99 | void barrier(void) { 100 | __asm__ __volatile__("": : :"memory"); 101 | } """ 102 | from pycparserext.ext_c_parser import GnuCParser 103 | p = GnuCParser() 104 | ast = p.parse(src) 105 | ast.show() 106 | 107 | from pycparserext.ext_c_generator import GnuCGenerator 108 | print(GnuCGenerator().visit(ast)) 109 | 110 | 111 | def test_asm_label(): 112 | src = """ 113 | int foo asm("renamed_foo"); 114 | 115 | unsigned long bar asm("renamed_bar") __attribute__ ((aligned (16))); 116 | 117 | unsigned long bar2 asm("renamed_bar2"); 118 | 119 | unsigned int * bar3 asm("renamed_bar3"); 120 | 121 | void func() { 122 | static int var asm("renamed_var") = 5; 123 | } 124 | """ 125 | assert _round_trip_matches(src) 126 | 127 | 128 | def test_register_asm_label(): 129 | from pycparser import c_ast 130 | 131 | from pycparserext.ext_c_parser import GnuCParser, TypeDeclExt 132 | src = 'register int my_var asm("my_reg");' 133 | p = GnuCParser() 134 | ast = p.parse(src) 135 | decl = ast.ext[0] 136 | assert isinstance(decl, c_ast.Decl) 137 | 138 | # The asm attribute should be on the TypeDeclExt, not the Decl 139 | assert not hasattr(decl, "asm") 140 | 141 | # The declarator part of the Decl should be a TypeDeclExt 142 | # with the asm attribute. 143 | assert isinstance(decl.type, TypeDeclExt) 144 | assert hasattr(decl.type, "asm") 145 | assert decl.type.asm is not None 146 | assert decl.type.asm.template.value == '"my_reg"' 147 | 148 | # Test round-trip: the main issue in #33 was that no C code was generated 149 | assert _round_trip_matches(src) 150 | 151 | # Test with __asm__ variant 152 | src2 = 'register int foo __asm__("bar");' 153 | assert _round_trip_matches(src2) 154 | 155 | # Test with different types 156 | src3 = 'register unsigned int counter asm("r0");' 157 | assert _round_trip_matches(src3) 158 | 159 | 160 | def test_pointer_with_attr(): 161 | # https://github.com/inducer/pycparserext/issues/86 162 | src = "typedef float * __attribute__((abc)) b;" 163 | assert _round_trip_matches(src) 164 | 165 | 166 | def test_funky_header_code(): 167 | src = """ 168 | extern __inline int __attribute__ ((__nothrow__)) __signbitf (float __x) 169 | { 170 | int __m; 171 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 172 | return __m & 0x8; 173 | } 174 | """ 175 | 176 | from pycparserext.ext_c_parser import GnuCParser 177 | p = GnuCParser() 178 | ast = p.parse(src) 179 | ast.show() 180 | 181 | from pycparserext.ext_c_generator import GnuCGenerator 182 | print(GnuCGenerator().visit(ast)) 183 | 184 | 185 | def test_funky_header_code_2(): 186 | src = """ 187 | extern __inline int __attribute__ ((__nothrow__)) __signbitf (float __x) 188 | { 189 | int __m; 190 | if (__x == 0) 191 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 192 | else 193 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 194 | return __m & 0x8; 195 | } 196 | """ 197 | 198 | from pycparserext.ext_c_parser import GnuCParser 199 | p = GnuCParser() 200 | ast = p.parse(src) 201 | ast.show() 202 | 203 | from pycparserext.ext_c_generator import GnuCGenerator 204 | print(GnuCGenerator().visit(ast)) 205 | 206 | 207 | def test_funky_header_code_3(): 208 | src = """ 209 | extern __inline int __attribute__ ((__nothrow__)) __signbitf (float __x) 210 | { 211 | int __m; 212 | if (__x == 0) 213 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 214 | return __m & 0x8; 215 | } 216 | """ 217 | 218 | from pycparserext.ext_c_parser import GnuCParser 219 | p = GnuCParser() 220 | ast = p.parse(src) 221 | ast.show() 222 | 223 | from pycparserext.ext_c_generator import GnuCGenerator 224 | print(GnuCGenerator().visit(ast)) 225 | 226 | 227 | def test_funky_header_code_4(): 228 | src = """ 229 | extern __inline int __attribute__ ((__nothrow__)) __signbitf (float __x) 230 | { 231 | int __m; 232 | if (__x == 0) { 233 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 234 | } else { 235 | __asm ("pmovmskb %1, %0" : "=r" (__m) : "x" (__x)); 236 | } 237 | return __m & 0x8; 238 | } 239 | """ 240 | 241 | from pycparserext.ext_c_parser import GnuCParser 242 | p = GnuCParser() 243 | ast = p.parse(src) 244 | ast.show() 245 | 246 | from pycparserext.ext_c_generator import GnuCGenerator 247 | print(GnuCGenerator().visit(ast)) 248 | 249 | 250 | def test_funky_header_code_5(): 251 | src = """ void do_foo(void) __asm(__STRING(do_foo));""" 252 | 253 | from pycparserext.ext_c_parser import GnuCParser 254 | p = GnuCParser() 255 | ast = p.parse(src) 256 | ast.show() 257 | 258 | from pycparserext.ext_c_generator import GnuCGenerator 259 | print(GnuCGenerator().visit(ast)) 260 | 261 | 262 | @pytest.mark.parametrize("typename", ["int", "uint"]) 263 | def test_opencl(typename): 264 | from pycparserext.ext_c_parser import OpenCLCParser 265 | src = """ 266 | __kernel void zeroMatrix(__global float *A, int n, __global float * B) 267 | { 268 | %s i = get_global_id(0); 269 | for (int k=0; k 1: 771 | exec(sys.argv[1]) 772 | else: 773 | from py.test.cmdline import main 774 | main([__file__]) 775 | -------------------------------------------------------------------------------- /pycparserext/ext_c_parser.py: -------------------------------------------------------------------------------- 1 | import pycparser.c_ast as c_ast 2 | import pycparser.c_parser 3 | 4 | 5 | try: 6 | import pycparser.ply.yacc as yacc 7 | except ImportError: 8 | import ply.yacc as yacc # noqa: F401 9 | from pycparser.plyparser import parameterized, template 10 | 11 | 12 | class CParserBase(pycparser.c_parser.CParser): 13 | def __init__(self, **kwds): 14 | kwds["lexer"] = self.lexer_class 15 | kwds["lextab"] = "pycparserext.lextab" 16 | kwds["yacctab"] = "pycparserext.yacctab" 17 | pycparser.c_parser.CParser.__init__(self, **kwds) 18 | 19 | def parse(self, text, filename="", debuglevel=0, 20 | initial_type_symbols=frozenset()): 21 | self.clex.filename = filename 22 | self.clex.reset_lineno() 23 | 24 | # _scope_stack[-1] is the current (topmost) scope. 25 | 26 | initial_scope = dict.fromkeys(initial_type_symbols, 1) 27 | initial_scope.update( 28 | dict.fromkeys(self.initial_type_symbols, 1) 29 | ) 30 | self._scope_stack = [initial_scope] 31 | 32 | if not text or text.isspace(): 33 | return c_ast.FileAST([]) 34 | else: 35 | return self.cparser.parse(text, lexer=self.clex, debug=debuglevel) 36 | 37 | 38 | # {{{ ast extensions 39 | 40 | class TypeList(c_ast.Node): 41 | def __init__(self, types, coord=None): 42 | self.types = types 43 | self.coord = coord 44 | 45 | def children(self): 46 | nodelist = [] 47 | for i, child in enumerate(self.types or []): 48 | nodelist.append(("types[%d]" % i, child)) 49 | return tuple(nodelist) 50 | 51 | def __iter__(self): 52 | yield from (self.types or []) 53 | 54 | attr_names = () 55 | 56 | 57 | class AttributeSpecifier(c_ast.Node): 58 | def __init__(self, exprlist): 59 | self.exprlist = exprlist 60 | 61 | def __eq__(self, other): 62 | if not isinstance(other, AttributeSpecifier): 63 | return False 64 | # Recursively compare the exprlist nodes 65 | return self._compare_ast_nodes(self.exprlist, other.exprlist) 66 | 67 | def _compare_ast_nodes(self, node1, node2): 68 | """Recursively compare two AST nodes for structural equality.""" 69 | if type(node1) is not type(node2): 70 | return False 71 | 72 | # Compare attributes 73 | if hasattr(node1, "attr_names"): 74 | for attr in node1.attr_names: 75 | val1 = getattr(node1, attr) 76 | val2 = getattr(node2, attr) 77 | if val1 != val2: 78 | return False 79 | 80 | # Compare children 81 | if hasattr(node1, "children"): 82 | children1 = node1.children() 83 | children2 = node2.children() 84 | if len(children1) != len(children2): 85 | return False 86 | for (name1, child1), (name2, child2) in zip(children1, children2): 87 | if name1 != name2: 88 | return False 89 | if not self._compare_ast_nodes(child1, child2): 90 | return False 91 | 92 | return True 93 | 94 | def children(self): 95 | return [("exprlist", self.exprlist)] 96 | 97 | def __iter__(self): 98 | # Do not return anything, but yield is necessary to keep this function 99 | # a generator 100 | return 101 | yield 102 | 103 | attr_names = () 104 | 105 | 106 | class Asm(c_ast.Node): 107 | def __init__(self, asm_keyword, template, output_operands, 108 | input_operands, clobbered_regs, coord=None): 109 | self.asm_keyword = asm_keyword 110 | self.template = template 111 | self.output_operands = output_operands 112 | self.input_operands = input_operands 113 | self.clobbered_regs = clobbered_regs 114 | self.coord = coord 115 | 116 | def children(self): 117 | nodelist = [] 118 | if self.template is not None: 119 | nodelist.append(("template", self.template)) 120 | if self.output_operands is not None: 121 | nodelist.append(("output_operands", self.output_operands)) 122 | if self.input_operands is not None: 123 | nodelist.append(("input_operands", self.input_operands)) 124 | if self.clobbered_regs is not None: 125 | nodelist.append(("clobbered_regs", self.clobbered_regs)) 126 | return tuple(nodelist) 127 | 128 | def __iter__(self): 129 | if self.template is not None: 130 | yield self.template 131 | if self.output_operands is not None: 132 | yield self.output_operands 133 | if self.input_operands is not None: 134 | yield self.input_operands 135 | if self.clobbered_regs is not None: 136 | yield self.clobbered_regs 137 | 138 | attr_names = ("asm_keyword",) 139 | 140 | 141 | class PreprocessorLine(c_ast.Node): 142 | def __init__(self, contents, coord=None): 143 | self.contents = contents 144 | self.coord = coord 145 | 146 | def children(self): 147 | return () 148 | 149 | def __iter__(self): 150 | # Do not return anything, but yield is necessary to keep this function 151 | # a generator 152 | return 153 | yield 154 | 155 | attr_names = ("contents",) 156 | 157 | 158 | class TypeOfDeclaration(c_ast.Node): 159 | def __init__(self, typeof_keyword, declaration, coord=None): 160 | self.typeof_keyword = typeof_keyword 161 | self.declaration = declaration 162 | self.coord = coord 163 | 164 | def children(self): 165 | nodelist = [] 166 | if self.declaration is not None: 167 | nodelist.append(("declaration", self.declaration)) 168 | return tuple(nodelist) 169 | 170 | def __iter__(self): 171 | if self.declaration is not None: 172 | yield self.declaration 173 | 174 | attr_names = ("typeof_keyword",) 175 | 176 | 177 | class TypeOfExpression(c_ast.Node): 178 | def __init__(self, typeof_keyword, expr, coord=None): 179 | self.typeof_keyword = typeof_keyword 180 | self.expr = expr 181 | self.coord = coord 182 | 183 | def children(self): 184 | nodelist = [] 185 | if self.expr is not None: 186 | nodelist.append(("expr", self.expr)) 187 | return tuple(nodelist) 188 | 189 | def __iter__(self): 190 | if self.expr is not None: 191 | yield self.expr 192 | 193 | attr_names = ("typeof_keyword",) 194 | 195 | 196 | class RangeExpression(c_ast.Node): 197 | def __init__(self, first, last, coord=None): 198 | self.first = first 199 | self.last = last 200 | self.coord = coord 201 | 202 | def children(self): 203 | nodelist = [] 204 | if self.first is not None: 205 | nodelist.append(("first", self.first)) 206 | if self.last is not None: 207 | nodelist.append(("last", self.last)) 208 | return tuple(nodelist) 209 | 210 | def __iter__(self): 211 | if self.first is not None: 212 | yield self.first 213 | if self.last is not None: 214 | yield self.last 215 | 216 | attr_names = () 217 | 218 | 219 | # These are the same as pycparser's, but it does *not* declare __slots__-- 220 | # so we can poke in attributes at our leisure. 221 | class TypeDeclExt(c_ast.TypeDecl): 222 | __slots__ = ("asm", "attributes", "init") 223 | 224 | @staticmethod 225 | def from_pycparser(td): 226 | assert isinstance(td, c_ast.TypeDecl) 227 | 228 | return TypeDeclExt( 229 | declname=td.declname, 230 | quals=td.quals, 231 | align=td.align, 232 | type=td.type, 233 | coord=td.coord 234 | ) 235 | 236 | 237 | class ArrayDeclExt(c_ast.ArrayDecl): 238 | __slots__ = ("asm", "attributes", "init") 239 | 240 | @staticmethod 241 | def from_pycparser(ad): 242 | assert isinstance(ad, c_ast.ArrayDecl) 243 | return ArrayDeclExt( 244 | type=ad.type, 245 | dim=ad.dim, 246 | dim_quals=ad.dim_quals, 247 | coord=ad.coord 248 | ) 249 | 250 | 251 | class StructExt(c_ast.Struct): 252 | """Extended Struct that can hold attributes.""" 253 | @staticmethod 254 | def from_pycparser(st): 255 | assert isinstance(st, c_ast.Struct) 256 | return StructExt( 257 | name=st.name, 258 | decls=st.decls, 259 | coord=st.coord 260 | ) 261 | 262 | 263 | def to_decl_ext(d): 264 | if isinstance(d, c_ast.TypeDecl): 265 | return TypeDeclExt.from_pycparser(d) 266 | elif isinstance(d, c_ast.ArrayDecl): 267 | return ArrayDeclExt.from_pycparser(d) 268 | else: 269 | raise TypeError("unexpected decl type: %s" % type(d).__name__) 270 | 271 | 272 | class FuncDeclExt(c_ast.Node): 273 | def __init__(self, args, type, attributes, asm, coord=None): 274 | self.args = args 275 | self.type = type 276 | self.attributes = attributes 277 | self.asm = asm 278 | self.coord = coord 279 | 280 | def children(self): 281 | nodelist = [] 282 | if self.args is not None: 283 | nodelist.append(("args", self.args)) 284 | if self.type is not None: 285 | nodelist.append(("type", self.type)) 286 | if self.attributes is not None: 287 | nodelist.append(("attributes", self.attributes)) 288 | if self.asm is not None: 289 | nodelist.append(("asm", self.asm)) 290 | return tuple(nodelist) 291 | 292 | def __iter__(self): 293 | if self.args is not None: 294 | yield self.args 295 | if self.type is not None: 296 | yield self.type 297 | if self.attributes is not None: 298 | yield self.attributes 299 | if self.asm is not None: 300 | yield self.asm 301 | 302 | attr_names = () 303 | 304 | # }}} 305 | 306 | 307 | # {{{ attributes 308 | 309 | class _AttributesMixin: 310 | def p_attributes_opt_1(self, p): 311 | """ attributes_opt : attribute_decl attributes_opt 312 | """ 313 | p[1].exprs.extend(p[2].exprs) 314 | p[0] = p[1] 315 | 316 | def p_attributes_opt_2(self, p): 317 | """ attributes_opt : empty 318 | """ 319 | p[0] = c_ast.ExprList([], self._coord(p.lineno(1))) 320 | 321 | def p_attribute_decl(self, p): 322 | """ attribute_decl : __ATTRIBUTE__ LPAREN LPAREN attribute_list RPAREN RPAREN 323 | | __ATTRIBUTE LPAREN LPAREN attribute_list RPAREN RPAREN 324 | """ 325 | p[0] = p[4] 326 | 327 | def p_attribute_list_1(self, p): 328 | """ attribute_list : attribute 329 | """ 330 | p[0] = c_ast.ExprList([p[1]], self._coord(p.lineno(1))) 331 | 332 | def p_attribute_list_2(self, p): 333 | """ attribute_list : attribute_list COMMA attribute 334 | """ 335 | p[1].exprs.append(p[3]) 336 | p[0] = p[1] 337 | 338 | def p_attribute_1(self, p): 339 | """ attribute : CONST 340 | """ 341 | p[0] = c_ast.ID(name="const", coord=self._coord(p.lineno(1))) 342 | 343 | def p_attribute_3(self, p): 344 | """ attribute : assignment_expression 345 | """ 346 | p[0] = p[1] 347 | 348 | def p_function_specifier_attr(self, p): 349 | """ function_specifier : attribute_decl 350 | """ 351 | p[0] = AttributeSpecifier(p[1]) 352 | 353 | # }}} 354 | 355 | 356 | # {{{ asm 357 | 358 | class _AsmMixin: 359 | def p_asm_opt_1(self, p): 360 | """ asm_opt : empty 361 | """ 362 | p[0] = None 363 | 364 | def p_asm_opt_2(self, p): 365 | """ asm_opt : asm_no_semi 366 | """ 367 | p[0] = p[1] 368 | 369 | def p_asm_1(self, p): 370 | """ asm_no_semi : asm_keyword LPAREN asm_argument_expression_list RPAREN 371 | """ 372 | p[0] = Asm(p[1], p[3], None, None, None, coord=self._coord(p.lineno(2))) 373 | 374 | def p_asm_2(self, p): 375 | """ asm_no_semi : asm_keyword LPAREN asm_argument_expression_list COLON \ 376 | asm_argument_expression_list RPAREN 377 | """ 378 | p[0] = Asm(p[1], p[3], p[5], None, None, coord=self._coord(p.lineno(2))) 379 | 380 | def p_asm_3(self, p): 381 | """ asm_no_semi : asm_keyword LPAREN asm_argument_expression_list COLON \ 382 | asm_argument_expression_list COLON asm_argument_expression_list \ 383 | RPAREN 384 | """ 385 | p[0] = Asm(p[1], p[3], p[5], p[7], None, coord=self._coord(p.lineno(2))) 386 | 387 | def p_asm_4(self, p): 388 | """ asm_no_semi : asm_keyword LPAREN asm_argument_expression_list COLON \ 389 | asm_argument_expression_list COLON asm_argument_expression_list \ 390 | COLON asm_argument_expression_list RPAREN 391 | """ 392 | p[0] = Asm(p[1], p[3], p[5], p[7], p[9], coord=self._coord(p.lineno(2))) 393 | 394 | def p_asm_keyword(self, p): 395 | """ asm_keyword : __ASM__ asm_volatile_opt 396 | | __ASM asm_volatile_opt 397 | | ASM asm_volatile_opt 398 | """ 399 | p[0] = p[1] 400 | if p[2]: 401 | p[0] += " " + p[2] 402 | 403 | def p_asm_volatile_opt(self, p): 404 | """ asm_volatile_opt : unified_volatile 405 | | empty 406 | """ 407 | p[0] = p[1] 408 | 409 | def p_asm_argument_expression_list(self, p): 410 | """asm_argument_expression_list : argument_expression_list 411 | | empty 412 | """ 413 | p[0] = p[1] 414 | 415 | def p_statement_asm(self, p): 416 | """ statement : asm_no_semi 417 | | asm_no_semi SEMI 418 | """ 419 | p[0] = p[1] 420 | 421 | def p_asm_label_opt(self, p): 422 | """ asm_label_opt : asm_keyword LPAREN unified_string_literal RPAREN 423 | | empty 424 | """ 425 | if p[1] is None: 426 | p[0] = None 427 | else: 428 | p[0] = Asm(p[1], p[3], None, None, None, coord=self._coord(p.lineno(2))) 429 | 430 | # }}} 431 | 432 | 433 | @template 434 | class _AsmAndAttributesMixin(_AsmMixin, _AttributesMixin): 435 | # {{{ /!\ names must match C parser to override 436 | 437 | @parameterized(("id", "ID"), ("typeid", "TYPEID"), ("typeid_noparen", "TYPEID")) 438 | def p_xxx_declarator_1(self, p): 439 | """ xxx_declarator : direct_xxx_declarator asm_label_opt attributes_opt 440 | """ 441 | if p[2] or p[3].exprs: 442 | if isinstance(p[1], (c_ast.ArrayDecl, c_ast.FuncDecl)): 443 | decl_ext = to_decl_ext(p[1].type) 444 | 445 | elif isinstance(p[1], c_ast.TypeDecl): 446 | decl_ext = to_decl_ext(p[1]) 447 | 448 | else: 449 | raise NotImplementedError( 450 | "cannot attach asm or attributes to nodes of type '%s'" 451 | % type(p[1])) 452 | 453 | if p[2]: 454 | decl_ext.asm = p[2] 455 | 456 | if p[3].exprs: 457 | decl_ext.attributes = p[3] 458 | 459 | p[1] = decl_ext 460 | 461 | p[0] = p[1] 462 | 463 | @parameterized(("id", "ID"), ("typeid", "TYPEID"), ("typeid_noparen", "TYPEID")) 464 | def p_xxx_declarator_2(self, p): 465 | """ xxx_declarator : pointer direct_xxx_declarator asm_label_opt \ 466 | attributes_opt 467 | | pointer attributes_opt direct_xxx_declarator \ 468 | asm_label_opt 469 | """ 470 | if hasattr(p[4], "exprs"): 471 | attr_decl = p[4] 472 | asm_label = p[3] 473 | decl = p[2] 474 | else: 475 | attr_decl = p[2] 476 | asm_label = p[4] 477 | decl = p[3] 478 | 479 | if asm_label or attr_decl.exprs: 480 | innermost_decl = decl 481 | while not isinstance(innermost_decl, c_ast.TypeDecl): 482 | try: 483 | innermost_decl = innermost_decl.type 484 | except AttributeError as err: 485 | raise NotImplementedError( 486 | "cannot attach asm or attributes to " 487 | "nodes of type '%s'" 488 | % type(innermost_decl) 489 | ) from err 490 | 491 | decl_ext = to_decl_ext(innermost_decl) 492 | 493 | if asm_label: 494 | decl_ext.asm = asm_label 495 | if attr_decl.exprs: 496 | decl_ext.attributes = attr_decl 497 | 498 | if innermost_decl is decl: 499 | decl = decl_ext 500 | else: 501 | parent = decl 502 | while parent.type is not innermost_decl: 503 | parent = parent.type 504 | parent.type = decl_ext 505 | 506 | p[0] = self._type_modify_decl(decl, p[1]) 507 | 508 | @parameterized(("id", "ID"), ("typeid", "TYPEID"), ("typeid_noparen", "TYPEID")) 509 | def p_direct_xxx_declarator_6(self, p): 510 | """ direct_xxx_declarator : direct_xxx_declarator \ 511 | LPAREN parameter_type_list RPAREN \ 512 | asm_opt attributes_opt 513 | | direct_xxx_declarator \ 514 | LPAREN identifier_list_opt RPAREN \ 515 | asm_label_opt attributes_opt 516 | """ 517 | func = FuncDeclExt( 518 | args=p[3], 519 | type=None, 520 | attributes=p[6], 521 | asm=p[5], 522 | coord=p[1].coord) 523 | 524 | p[0] = self._type_modify_decl(decl=p[1], modifier=func) 525 | 526 | def p_direct_abstract_declarator_6(self, p): 527 | """ direct_abstract_declarator : direct_abstract_declarator \ 528 | LPAREN parameter_type_list_opt RPAREN asm_label_opt attributes_opt 529 | """ 530 | func = FuncDeclExt( 531 | args=p[3], 532 | type=None, 533 | attributes=p[6], 534 | asm=p[5], 535 | coord=p[1].coord) 536 | 537 | p[0] = self._type_modify_decl(decl=p[1], modifier=func) 538 | 539 | # }}} 540 | # }}} 541 | 542 | 543 | # {{{ gnu parser 544 | 545 | class GnuCParser(_AsmAndAttributesMixin, CParserBase): 546 | # TODO: __extension__ 547 | 548 | from pycparserext.ext_c_lexer import GnuCLexer as lexer_class # noqa 549 | 550 | initial_type_symbols = frozenset({"__builtin_va_list"}) 551 | 552 | def p_function_specifier_gnu(self, p): 553 | """ function_specifier : __INLINE 554 | | __INLINE__ 555 | """ 556 | p[0] = p[1] 557 | 558 | def p_type_qualifier_gnu(self, p): 559 | """ type_qualifier : __CONST 560 | | __RESTRICT 561 | | __RESTRICT__ 562 | | __EXTENSION__ 563 | | __VOLATILE 564 | | __VOLATILE__ 565 | """ 566 | p[0] = p[1] 567 | 568 | def p_type_specifier_gnu_typeof_expr(self, p): 569 | """ type_specifier : __TYPEOF__ LPAREN expression RPAREN 570 | | TYPEOF LPAREN expression RPAREN 571 | | __TYPEOF LPAREN expression RPAREN 572 | """ 573 | if isinstance(p[3], c_ast.TypeDecl): 574 | pass 575 | 576 | p[0] = TypeOfExpression(p[1], p[3]) 577 | 578 | def p_type_specifier_gnu_typeof_decl(self, p): 579 | """ type_specifier : __TYPEOF__ LPAREN parameter_declaration RPAREN 580 | | TYPEOF LPAREN parameter_declaration RPAREN 581 | | __TYPEOF LPAREN parameter_declaration RPAREN 582 | """ 583 | p[0] = TypeOfDeclaration(p[1], p[3]) 584 | 585 | def p_unary_operator_gnu(self, p): 586 | """ unary_operator : __REAL__ 587 | | __IMAG__ 588 | """ 589 | p[0] = p[1] 590 | 591 | def p_postfix_expression_gnu_tcp(self, p): 592 | """ postfix_expression : __BUILTIN_TYPES_COMPATIBLE_P \ 593 | LPAREN parameter_declaration COMMA parameter_declaration RPAREN 594 | """ 595 | p[0] = c_ast.FuncCall(c_ast.ID(p[1], self._coord(p.lineno(1))), 596 | TypeList([p[3], p[5]], self._coord(p.lineno(2)))) 597 | 598 | def p_gnu_statement_expression(self, p): 599 | """ gnu_statement_expression : LPAREN compound_statement RPAREN 600 | """ 601 | p[0] = p[2] 602 | 603 | def p_gnu_primary_expression_6(self, p): 604 | """ primary_expression : gnu_statement_expression """ 605 | p[0] = p[1] 606 | 607 | def p_gnu_unary_expression(self, p): 608 | """ unary_expression : __ALIGNOF__ LPAREN type_name RPAREN 609 | """ 610 | p[0] = c_ast.UnaryOp( 611 | p[1], 612 | p[2] if len(p) == 3 else p[3], 613 | self._token_coord(p, 1)) 614 | 615 | def p_specifier_qualifier_list_fs(self, p): 616 | """ specifier_qualifier_list : function_specifier specifier_qualifier_list 617 | """ 618 | self._p_specifier_qualifier_list_left_recursion(p) 619 | 620 | def _p_specifier_qualifier_list_left_recursion(self, p): 621 | # The PLY documentation says that left-recursive rules are supported, 622 | # but it keeps complaining about reduce/reduce conflicts. 623 | # 624 | # See `_p_specifier_qualifier_list_right_recursion` for a non-complaining 625 | # version. 626 | spec = p[1] 627 | spec_dict = p[2] 628 | 629 | if isinstance(spec, AttributeSpecifier): 630 | spec_dict["function"].append(spec) 631 | elif isinstance(spec, str): 632 | spec_dict["qual"].append(spec) 633 | elif isinstance(spec, c_ast.Node): 634 | if "type" not in spec_dict: 635 | spec_dict["type"] = [] 636 | spec_dict["type"].append(spec) 637 | else: 638 | raise TypeError(f"Unknown specifier {spec!r} of type {type(spec)}") 639 | 640 | p[0] = spec_dict 641 | 642 | def p_statement(self, p): 643 | """ statement : labeled_statement 644 | | expression_statement 645 | | compound_statement 646 | | selection_statement 647 | | iteration_statement 648 | | jump_statement 649 | | pppragma_directive 650 | | gnu_statement_expression 651 | """ 652 | p[0] = p[1] 653 | 654 | def p_attribute_const(self, p): 655 | """ attribute : __CONST 656 | """ 657 | p[0] = c_ast.ID(name="__const", coord=self._coord(p.lineno(1))) 658 | 659 | def p_struct_declaration_list_1(self, p): 660 | """ struct_declaration_list : empty """ 661 | p[0] = None 662 | 663 | # Support attributes on anonymous struct/union declarations 664 | # e.g., struct { int a; } __attribute__((packed)); 665 | def p_struct_declaration_anonymous_with_attr(self, p): 666 | """ struct_declaration : specifier_qualifier_list attributes_opt SEMI 667 | """ 668 | spec = p[1] 669 | attr_decl = p[2] 670 | assert "typedef" not in spec["storage"] 671 | 672 | # Anonymous struct/union with attributes 673 | if len(spec["type"]) == 1: 674 | node = spec["type"][0] 675 | if isinstance(node, c_ast.Node): 676 | decl_type = node 677 | 678 | # If we have attributes and this is a Struct/Union, attach them 679 | if attr_decl and attr_decl.exprs and isinstance(node, c_ast.Struct): 680 | # Convert to StructExt and attach attributes 681 | if not isinstance(node, StructExt): 682 | struct_ext = StructExt.from_pycparser(node) 683 | struct_ext.attrib = AttributeSpecifier(attr_decl) 684 | decl_type = struct_ext 685 | else: 686 | node.attrib = AttributeSpecifier(attr_decl) 687 | else: 688 | decl_type = c_ast.IdentifierType(node) 689 | 690 | decls = self._build_declarations( 691 | spec=spec, 692 | decls=[{"decl": decl_type}]) 693 | else: 694 | # Structure/union members can have the same names as typedefs 695 | decls = self._build_declarations( 696 | spec=spec, 697 | decls=[{"decl": None, "init": None}]) 698 | 699 | p[0] = decls 700 | 701 | def p_range_designator(self, p): 702 | """ designator : LBRACKET constant_expression \ 703 | ELLIPSIS constant_expression RBRACKET 704 | """ 705 | p[0] = RangeExpression(p[2], p[4], coord=self._coord(p.lineno(1))) 706 | 707 | def p_labeled_statement_4(self, p): 708 | """ labeled_statement : CASE constant_expression \ 709 | ELLIPSIS constant_expression \ 710 | COLON pragmacomp_or_statement 711 | """ 712 | p[0] = c_ast.Case( 713 | RangeExpression(p[2], p[4], coord=self._coord(p.lineno(1))), 714 | [p[6]], 715 | self._coord(p.lineno(1))) 716 | 717 | def p_unified_volatile_gnu(self, p): 718 | """ unified_volatile : VOLATILE 719 | | __VOLATILE 720 | | __VOLATILE__ 721 | """ 722 | p[0] = p[1] 723 | 724 | # Support attributes on struct/union types 725 | # struct __attribute__((packed)) { int a; }; 726 | def p_struct_or_union_specifier_with_attr_1(self, p): 727 | """struct_or_union_specifier : struct_or_union attributes_opt \ 728 | brace_open struct_declaration_list brace_close 729 | | struct_or_union attributes_opt \ 730 | brace_open brace_close 731 | """ 732 | klass = self._select_struct_union_class(p[1]) 733 | attr_decl = p[2] 734 | 735 | if len(p) == 5: 736 | # Empty struct/union 737 | struct = klass( 738 | name=None, 739 | decls=[], 740 | coord=self._token_coord(p, 1)) 741 | else: 742 | struct = klass( 743 | name=None, 744 | decls=p[4], 745 | coord=self._token_coord(p, 1)) 746 | 747 | # Attach attributes if present 748 | if attr_decl and attr_decl.exprs: 749 | struct_ext = StructExt.from_pycparser(struct) 750 | struct_ext.attrib = AttributeSpecifier(attr_decl) 751 | p[0] = struct_ext 752 | else: 753 | p[0] = struct 754 | 755 | def p_struct_or_union_specifier_with_attr_2(self, p): 756 | """struct_or_union_specifier : struct_or_union ID attributes_opt \ 757 | brace_open struct_declaration_list brace_close 758 | | struct_or_union ID attributes_opt \ 759 | brace_open brace_close 760 | | struct_or_union TYPEID \ 761 | attributes_opt brace_open struct_declaration_list brace_close 762 | | struct_or_union TYPEID \ 763 | attributes_opt brace_open brace_close 764 | """ 765 | klass = self._select_struct_union_class(p[1]) 766 | attr_decl = p[3] 767 | 768 | if len(p) == 6: 769 | # Empty struct/union with name 770 | struct = klass( 771 | name=p[2], 772 | decls=[], 773 | coord=self._token_coord(p, 2)) 774 | else: 775 | struct = klass( 776 | name=p[2], 777 | decls=p[5], 778 | coord=self._token_coord(p, 2)) 779 | 780 | # Attach attributes if present 781 | if attr_decl and attr_decl.exprs: 782 | struct_ext = StructExt.from_pycparser(struct) 783 | struct_ext.attrib = AttributeSpecifier(attr_decl) 784 | p[0] = struct_ext 785 | else: 786 | p[0] = struct 787 | # }}} 788 | 789 | 790 | class OpenCLCParser(_AsmAndAttributesMixin, CParserBase): 791 | from pycparserext.ext_c_lexer import OpenCLCLexer as lexer_class # noqa 792 | 793 | INT_BIT_COUNTS = (8, 16, 32, 64) 794 | initial_type_symbols = ( 795 | { 796 | "%s%d" % (base_name, count) 797 | for base_name in [ 798 | "char", "uchar", "short", "ushort", "int", "uint", 799 | "long", "ulong", "float", "double", "half"] 800 | for count in [2, 3, 4, 8, 16] 801 | } 802 | | { 803 | "intptr_t", "uintptr_t", 804 | "intmax_t", "uintmax_t", 805 | "size_t", "ptrdiff_t", 806 | "uint", "ulong", "ushort", "uchar", 807 | "half", "bool"} 808 | | {"int%d_t" % bc for bc in INT_BIT_COUNTS} 809 | | {"uint%d_t" % bc for bc in INT_BIT_COUNTS} 810 | | {"int_least%d_t" % bc for bc in INT_BIT_COUNTS} 811 | | {"uint_least%d_t" % bc for bc in INT_BIT_COUNTS} 812 | | {"int_fast%d_t" % bc for bc in INT_BIT_COUNTS} 813 | | {"uint_fast%d_t" % bc for bc in INT_BIT_COUNTS} 814 | | { 815 | "image1d_t", "image1d_array_t", "image1d_buffer_t", 816 | "image2d_t", "image2d_array_t", 817 | "image3d_t", 818 | "sampler_t", "event_t" 819 | } 820 | | {"cfloat_t", "cdouble_t"} # PyOpenCL extension 821 | ) 822 | 823 | def p_pp_directive(self, p): 824 | """ pp_directive : PPHASH 825 | """ 826 | p[0] = [PreprocessorLine(p[1], coord=self._coord(p.lineno(1)))] 827 | 828 | def p_external_declaration_comment(self, p): 829 | """ external_declaration : LINECOMMENT 830 | """ 831 | p[0] = None 832 | 833 | def p_statement_comment(self, p): 834 | """ statement : LINECOMMENT 835 | """ 836 | p[0] = None 837 | 838 | def p_type_qualifier_cl(self, p): 839 | """ type_qualifier : __GLOBAL 840 | | GLOBAL 841 | | __LOCAL 842 | | LOCAL 843 | | __CONSTANT 844 | | CONSTANT 845 | | __PRIVATE 846 | | PRIVATE 847 | | __READ_ONLY 848 | | READ_ONLY 849 | | __WRITE_ONLY 850 | | WRITE_ONLY 851 | | __READ_WRITE 852 | | READ_WRITE 853 | """ 854 | p[0] = p[1] 855 | 856 | def p_function_specifier_cl(self, p): 857 | """ function_specifier : __KERNEL 858 | | KERNEL 859 | """ 860 | p[0] = p[1] 861 | 862 | def p_unified_volatile_cl(self, p): 863 | """ unified_volatile : VOLATILE 864 | """ 865 | p[0] = p[1] 866 | 867 | # vim: fdm=marker 868 | --------------------------------------------------------------------------------