Skip to content
Merged
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
8 changes: 5 additions & 3 deletions simplecpreprocessor/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,8 @@ def process_include(self, **kwargs):
header = item[item.index("\"")+1:-1]
elif item.startswith('"') and item.endswith('"'):
header = item.strip('"')
else:
else: # pragma: no cover
# Defensive: tokenizer ensures STRING tokens are well-formed
fmt = (
"Invalid include on line %s, got %r for include name"
% (line_no, item)
Expand Down Expand Up @@ -439,11 +440,12 @@ def process_include(self, **kwargs):
)
raise exceptions.ParseError(fmt)
parts.append(tok.value)
fmt = (
# Defensive: tokenizer ensures chunks end with NEWLINE
fmt = ( # pragma: no cover
"Invalid include on line %s, missing '>'"
% line_no
)
raise exceptions.ParseError(fmt)
raise exceptions.ParseError(fmt) # pragma: no cover

fmt = (
"Invalid include on line %s, got %r for include name"
Expand Down
12 changes: 12 additions & 0 deletions simplecpreprocessor/tests/test_basic_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,15 @@ def test_string_folding_inside_condition():
])
ret = preprocess(f_obj, fold_strings_to_null=True)
assert "".join(ret) == "const char* foo = NULL;\n"


def test_empty_lines():
"""Test that empty lines are handled correctly."""
f_obj = FakeFile("header.h", [
"#define FOO 1\n",
"\n",
"\n",
"FOO\n"
])
expected = "\n\n1\n"
run_case(f_obj, expected)
8 changes: 8 additions & 0 deletions simplecpreprocessor/tests/test_defines_and_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,11 @@ def test_repeated_macro():
'A A\n', ])
ret = preprocess(f_obj)
assert "".join(ret) == "value value\n"


def test_pragma_whitespace_only():
"""Test pragma with only whitespace after the directive."""
f_obj = FakeFile("header.h", ["#pragma \n"])
with pytest.raises(ParseError) as excinfo:
"".join(preprocess(f_obj))
assert "Unsupported pragma" in str(excinfo.value)
32 changes: 32 additions & 0 deletions simplecpreprocessor/tests/test_if_elif.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,3 +394,35 @@ def test_elif_with_invalid_expression():
])
with pytest.raises(ParseError, match="Error evaluating #elif"):
"".join(preprocess(f_obj))


def test_nested_elif_outer_false():
"""Test elif inside a false outer if condition."""
f_obj = FakeFile("header.h", [
"#if 0\n",
"#if 0\n",
"A\n",
"#elif 1\n",
"B\n",
"#endif\n",
"#endif\n"
])
expected = ""
run_case(f_obj, expected)


def test_deeply_nested_elif_inner():
"""Test elif deep inside nested conditionals with active parents."""
f_obj = FakeFile("header.h", [
"#if 1\n", # level 0: active
"#if 1\n", # level 1: active
"#if 0\n", # level 2: not active
"A\n",
"#elif 1\n", # This should be active since parents are active
"B\n",
"#endif\n",
"#endif\n",
"#endif\n"
])
expected = "B\n"
run_case(f_obj, expected)
50 changes: 50 additions & 0 deletions simplecpreprocessor/tests/test_includes_and_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,53 @@ def test_no_fullfile_guard_ifndef():
assert not preprocessor.skip_file("other.h"), (
"%s -> %s" % (preprocessor.include_once,
preprocessor.defines))


def test_include_u8_string():
"""Test include with u8 prefix (UTF-8 string literal)."""
f_obj = FakeFile("header.h", ['#include u8"other.h"\n'])
handler = FakeHandler({"other.h": ["1\n"]})
ret = preprocess(f_obj, header_handler=handler)
assert "".join(ret) == "1\n"


def test_include_u_string():
"""Test include with u prefix (char16_t string literal)."""
f_obj = FakeFile("header.h", ['#include u"other.h"\n'])
handler = FakeHandler({"other.h": ["1\n"]})
ret = preprocess(f_obj, header_handler=handler)
assert "".join(ret) == "1\n"


def test_include_capital_u_string():
"""Test include with U prefix (char32_t string literal)."""
f_obj = FakeFile("header.h", ['#include U"other.h"\n'])
handler = FakeHandler({"other.h": ["1\n"]})
ret = preprocess(f_obj, header_handler=handler)
assert "".join(ret) == "1\n"


def test_include_l_string():
"""Test include with L prefix (wide string literal)."""
f_obj = FakeFile("header.h", ['#include L"other.h"\n'])
handler = FakeHandler({"other.h": ["1\n"]})
ret = preprocess(f_obj, header_handler=handler)
assert "".join(ret) == "1\n"


def test_include_invalid_string_format():
"""Test include with invalid string format (no closing quote)."""
f_obj = FakeFile("header.h", ['#include "other.h\n'])
with pytest.raises(ParseError) as excinfo:
"".join(preprocess(f_obj))
assert "Invalid include" in str(excinfo.value)


def test_include_angle_bracket_eof_no_newline():
"""Test include with angle bracket missing '>' at EOF after tokens."""
# Simulate the case where the loop exhausts without finding '>'
# This happens when there's content but no '>' and no newline at EOF
f_obj = FakeFile("header.h", ['#include <other.h extra'])
with pytest.raises(ParseError) as excinfo:
"".join(preprocess(f_obj))
assert "missing '>'" in str(excinfo.value)
6 changes: 4 additions & 2 deletions simplecpreprocessor/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,8 @@ def _cb(s, t):
def _scan_line(self, line_no, line):
self.line_no = line_no
tokens, remainder = self._scanner.scan(line)
if remainder:
if remainder: # pragma: no cover
# Defensive: scanner patterns should match all input
raise SyntaxError(
f"Unrecognized input: {remainder!r}"
)
Expand All @@ -238,7 +239,8 @@ def __iter__(self):
tokens = self._scan_line(line_no, line)
try:
token = next(tokens)
except StopIteration:
except StopIteration: # pragma: no cover
# Defensive: scanner always produces at least NEWLINE
continue # skip empty lines

lookahead = None
Expand Down