Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Improve C/C++ preprocessing #369

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 0 additions & 37 deletions lizard_ext/lizardcpre.py

This file was deleted.

61 changes: 61 additions & 0 deletions lizard_ext/lizardcpreprocess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
'''
This is an extension of lizard,
It helps to deal with C code with preprocessors that
is hard to parse. It works by checking the first if
condition and deciding to take that or the final else
'''

import re

class LizardExtension(object): # pylint: disable=R0903

ordering_index = 0
macro_pattern = re.compile(r"#\s*(\w+)\s*(.*)", re.M | re.S)

def __call__(self, tokens, reader):
def preprocess_tokens(tokens):
if_stack = [] # if-like directive with condition
directive_stack = [] # current directive, unstacked on endif

for token in tokens:
macro = self.macro_pattern.match(token)
if macro:
directive = macro.group(1)
_update_stacks(token, directive, if_stack, directive_stack)
yield from _blank_lines(token)
elif directive_stack:
if_condition = if_stack[-1]
directive = directive_stack[-1]
yield from _handle_condition(token, if_condition, directive)
else:
yield token

def _update_stacks(token, directive, ifs, directives):
if directive in ('if', 'ifdef', 'ifndef'): # push on the stack
ifs.append(token)
directives.append('if')
elif directive in ('elif', 'else'): # replace the directive
directives.pop()
directives.append(directive)
elif directive == 'endif': # pop from stack
ifs.pop()
directives.pop()

def _filter_token(token, directive, keep):
if directive == keep:
return token
return _blank_lines(token)

def _handle_condition(token, if_condition, directive):
keep = 'if' # default
if if_condition.startswith("#if 0"): # skip if, take else
keep = 'else'
return _filter_token(token, directive, keep)

def _blank_lines(token):
for _ in range(token.count('\n')):
yield '\n'

if "c" not in reader.ext:
return tokens
return preprocess_tokens(tokens)
43 changes: 42 additions & 1 deletion test/testCPreprocessorExtension.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from mock import Mock, patch
from lizard_ext.lizardcpre import LizardExtension as CPreprocessor
from lizard_ext.lizardcpreprocess import LizardExtension as CPreprocessor
from .testHelpers import get_cpp_function_list_with_extension
from lizard_languages.code_reader import CodeReader
generate_tokens = CodeReader.generate_tokens
Expand Down Expand Up @@ -78,6 +78,47 @@ def test_should_handle_nested_ifs(self):
""")
self.assertIn("1", tokens)

def test_should_handle_take_if_branch_on_ifndef(self):
tokens = process_code("""
#ifndef BLAH
1
#endif
""")
self.assertIn("1", tokens)

def test_should_handle_take_if_branch_on_if1(self):
tokens = process_code("""
#if 1
1
#else
2
#endif
""")
self.assertIn("1", tokens)
self.assertNotIn("2", tokens)

def test_should_handle_take_if_branch_on_if1_with_space_after_hash(self):
tokens = process_code("""
# if 1
1
# else
2
# endif
""")
self.assertIn("1", tokens)
self.assertNotIn("2", tokens)

def test_should_handle_take_else_branch_on_if0(self):
tokens = process_code("""
#if 0
1
#else
2
#endif
""")
self.assertNotIn("1", tokens)
self.assertIn("2", tokens)

def test_should_handle_multiple_elifs(self):
tokens = process_code("""
#if defined(BLAH1)
Expand Down