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
2 changes: 2 additions & 0 deletions c_parser/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ class CFunction:
specifiers: list[str] = field(default_factory=list)
variadic: bool = False
is_definition: bool = False
prototype_style: str | None = None
source_location: CSourceLocation | None = None


Expand Down Expand Up @@ -284,6 +285,7 @@ class CMacro:
name: str
value: str | None = None
function_like: bool = False
directive: str = "define"
source_location: CSourceLocation | None = None


Expand Down
68 changes: 68 additions & 0 deletions c_parser/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from .lexer import (
CTopLevelSegment,
split_top_level_c_source,
strip_c_comments,
top_level_partition,
top_level_split,
)
Expand Down Expand Up @@ -304,6 +305,70 @@ def _is_knr_definition(self, segment: CTopLevelSegment, parameters_text: str) ->
return False
return True

def _raise_for_unsupported_old_style_definitions(
self,
source: str,
filename: str | None,
) -> None:
source_lines = source.splitlines()
stripped_lines = strip_c_comments(source).splitlines()

for index, line in enumerate(stripped_lines):
text = line.strip()
parameter_bounds = self._find_parameter_list(text)
if parameter_bounds is None:
continue
open_index, close_index = parameter_bounds
before_parameters = text[:open_index].strip()
name_match = self._last_identifier(before_parameters)
if name_match is None:
continue
return_spec = before_parameters[: name_match.start()].strip()
if not return_spec or "(" in return_spec or ")" in return_spec:
continue

parameters_text = text[open_index + 1 : close_index].strip()
if not parameters_text or parameters_text == "void":
continue

parameters = [part.strip() for part in parameters_text.split(",")]
if not parameters or not all(re.fullmatch(r"[A-Za-z_]\w*", part) for part in parameters):
continue

saw_old_style_declaration = False
for follow in stripped_lines[index + 1 :]:
stripped = follow.strip()
if not stripped:
continue
if stripped.startswith("{"):
source_line = source_lines[index] if index < len(source_lines) else line
raise CParseError(
"K&R style function definitions are not supported",
filename=filename,
line_number=index + 1,
column=max(line.find(name_match.group(0)) + 1, 1),
source_line=source_line,
code="CPARSE002",
)
if stripped.endswith(";"):
saw_old_style_declaration = True
continue
break

if saw_old_style_declaration:
source_line = source_lines[index] if index < len(source_lines) else line
raise CParseError(
"K&R style function definitions are not supported",
filename=filename,
line_number=index + 1,
column=max(line.find(name_match.group(0)) + 1, 1),
source_line=source_line,
code="CPARSE002",
)

def _prototype_style(self, parameters_text: str) -> str:
return "unspecified" if not parameters_text.strip() else "prototype"

def _parse_function(self, segment: CTopLevelSegment) -> CFunction | None:
text = segment.text.strip()
if text.startswith(("typedef ", "struct ", "union ", "enum ")):
Expand Down Expand Up @@ -347,6 +412,7 @@ def _parse_function(self, segment: CTopLevelSegment) -> CFunction | None:
specifiers=function_specifiers,
variadic=variadic,
is_definition=segment.terminator == "block",
prototype_style=self._prototype_style(parameters_text),
source_location=self._source_location(segment),
)

Expand Down Expand Up @@ -387,6 +453,8 @@ def _parse_translation_unit(
source: str,
filename: str | None,
) -> tuple[list[CFunction], list[CTypedef], list[CGlobal]]:
self._raise_for_unsupported_old_style_definitions(source, filename)

functions: list[CFunction] = []
typedefs: list[CTypedef] = []
globals_: list[CGlobal] = []
Expand Down
13 changes: 13 additions & 0 deletions c_parser/preprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

_INCLUDE_RE = re.compile(r'^\s*#\s*include\s*(?:"([^"]+)"|<([^>]+)>)')
_DEFINE_RE = re.compile(r"^\s*#\s*define\s+([A-Za-z_]\w*)(\([^)]*\))?(?:\s+(.*))?$")
_UNDEF_RE = re.compile(r"^\s*#\s*undef\s+([A-Za-z_]\w*)\s*$")


@dataclass
Expand Down Expand Up @@ -121,6 +122,18 @@ def collect_preprocessor_metadata(
unit_name=name,
)
)
continue

undef_match = _UNDEF_RE.match(record.text)
if undef_match:
name = undef_match.group(1)
metadata.macros.append(
CMacro(
name=name,
directive="undef",
source_location=_record_location(record),
)
)

return metadata

Expand Down
31 changes: 20 additions & 11 deletions docs/c_parser/c_parser_architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Status: partial parser plus raw directive metadata implemented. The `c_parser`
package, typed parser models, public entrypoints, explicit
`x2py --language c --parse` CLI path, raw include/macro metadata collection,
top-level source splitting, and a first simple declaration/function subset
exist.
`x2py --language c --parse` CLI path, raw include/macro/undef metadata
collection, top-level source splitting, and a first simple
declaration/function subset exist.

This document records the target architecture for the C parser frontend in
x2py. The initial skeleton has grown into a partial parser, and the remaining
Expand All @@ -27,16 +27,22 @@ Implemented now:
records, exposes lightweight token records, and provides top-level splitting
helpers that track braces, parentheses, brackets, and literals.
- `c_parser.preprocessor` records raw `#include` directives, simple object-like
macros, and unsupported function-like macro diagnostics without expanding
macros.
macros, `#undef` directives, and unsupported function-like macro diagnostics
without expanding macros.
- `c_parser.parser` parses simple globals, typedefs, function prototypes, and
function-definition signatures while skipping bodies.
function-definition signatures while skipping bodies. Function models include
`prototype_style`, and K&R-style function definitions raise focused
diagnostics.
- `c_parser.cli` provides C-specific partial report formatting.
- `x2py.cli` dispatches `--language c --parse` to the C parser path.
- `--language c --semantics`, `--language c --pyi`, and C wrap-readiness are
rejected until semantic conversion exists.
- Focused partial CLI/API, declaration/function, and raw lexer/directive tests are
unskipped while broader roadmap tests remain skipped.
- Focused partial CLI/API, declaration/function, diagnostic color, and raw
lexer/directive tests are unskipped while broader roadmap tests remain
skipped.
- `tests/data/c/` contains C fixture scaffolding and general fixtures modeled
after the Fortran general fixture themes, with additional C-specific API
shapes.

Deferred:

Expand Down Expand Up @@ -185,16 +191,18 @@ Current and planned responsibilities:
them.
- `c_parser/preprocessor.py`
- Implemented: lightweight raw directive metadata for includes,
object-like macros, function-like macro diagnostics, and local include
resolution when a matching file is available.
object-like macros, `#undef` directives, function-like macro diagnostics,
and local include resolution when a matching file is available.
- Planned: compiler-assisted preprocessing metadata and `#line`/linemarker
source mapping for preprocessed input.
- `c_parser/parser.py`
- Implemented: `CParser`, `parse_c_file`, `parse_c_project`,
translation-unit visiting, simple declaration/function visitors, simple
declaration-specifier handling, and simple pointer/array declarator
extraction. Helper methods live on `CParser` rather than as broad
module-level functions.
module-level functions. Current function models record prototype-style
versus unspecified empty parameter lists, and K&R-style definitions are
rejected with `CParseError`.
- Planned: recursive declarator/function/composite-type visitors and a
richer shared declaration/declarator backend.
- `c_parser/project.py`
Expand Down Expand Up @@ -452,6 +460,7 @@ Raw-source mode target:
- Fold backslash-newline continuations.
- Record `#include` directives as structured include dependencies.
- Record `#define` object-like macros for simple constants.
- Record `#undef` directives as macro provenance.
- Record function-like macros as unsupported or deferred metadata.
- Record conditional directive presence as metadata only when needed for
provenance.
Expand Down
11 changes: 7 additions & 4 deletions docs/c_parser/c_parser_cli_workflow.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

Status: C parser partial subset plus raw directive metadata implemented. The
CLI command shape exists and parse reports can include raw includes, simple
macros, metadata diagnostics, simple globals, typedefs, function prototypes,
and function-definition signatures.
macros, `#undef` provenance, metadata diagnostics, simple globals, typedefs,
function prototypes, prototype-style metadata, and function-definition
signatures.

The C parser CLI workflow should be designed before parser implementation so
future parser work lands behind a stable command shape, output schema, and
Expand All @@ -30,7 +31,8 @@ top-level sections: `functions`, `structs`, `unions`, `enums`, `typedefs`,
can populate `functions`, `typedefs`, and `globals` for the supported subset,
while composite type sections remain empty. Raw `includes`, `macros`, and
metadata `diagnostics` can also be populated. The parser reports
`parser_status: "partial"`.
`parser_status: "partial"`. C parse diagnostics, currently including
unsupported K&R-style function definitions, honor `--no-color` and `NO_COLOR=1`.

Unsupported C stages:

Expand Down Expand Up @@ -202,7 +204,8 @@ JSON output for a file without raw directives:
"storage": [],
"specifiers": [],
"variadic": false,
"is_definition": false
"is_definition": false,
"prototype_style": "prototype"
}
],
"structs": [],
Expand Down
Loading
Loading