2760 lines
107 KiB
Python
2760 lines
107 KiB
Python
# ------------------------------------------------------------------------------
|
|
# pycparser: c_parser.py
|
|
#
|
|
# Recursive-descent parser for the C language.
|
|
#
|
|
# Eli Bendersky [https://eli.thegreenplace.net/]
|
|
# License: BSD
|
|
# ------------------------------------------------------------------------------
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from typing import (
|
|
Any,
|
|
Literal,
|
|
NoReturn,
|
|
NotRequired,
|
|
Optional,
|
|
TypedDict,
|
|
cast,
|
|
)
|
|
|
|
from . import c_ast
|
|
from .ast_transforms import fix_atomic_specifiers, fix_switch_cases
|
|
from .c_lexer import CLexer, Dialect, Token
|
|
|
|
|
|
# ASTs contain many coordinates, so slots reduce their memory overhead.
|
|
@dataclass(slots=True)
|
|
class Coord:
|
|
"""Coordinates of a syntactic element. Consists of:
|
|
- File name
|
|
- Line number
|
|
- Column number
|
|
"""
|
|
|
|
file: str
|
|
line: int
|
|
column: int | None = None
|
|
|
|
def __str__(self) -> str:
|
|
text = f"{self.file}:{self.line}"
|
|
if self.column is not None:
|
|
text += f":{self.column}"
|
|
return text
|
|
|
|
|
|
class ParseError(Exception):
|
|
pass
|
|
|
|
|
|
class CParser:
|
|
"""Recursive-descent C parser.
|
|
|
|
Usage:
|
|
parser = CParser()
|
|
ast = parser.parse(text, filename)
|
|
|
|
The `lexer` parameter lets you inject a lexer class (defaults to CLexer).
|
|
The positional parameters other than `lexer` are accepted for backward
|
|
compatibility with the old PLY-based parser and are otherwise unused.
|
|
|
|
The keyword-only `dialect` parameter selects keyword recognition: "legacy"
|
|
preserves existing behavior, while "c23" enables C23 spellings, predefined
|
|
constants, and other C23-only features.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
lex_optimize: bool = True,
|
|
lexer: type[CLexer] = CLexer,
|
|
lextab: str = "pycparser.lextab",
|
|
yacc_optimize: bool = True,
|
|
yacctab: str = "pycparser.yacctab",
|
|
yacc_debug: bool = False,
|
|
taboutputdir: str = "",
|
|
*,
|
|
dialect: Dialect = "legacy",
|
|
) -> None:
|
|
self.clex: CLexer = lexer(
|
|
error_func=self._lex_error_func,
|
|
dialect=dialect,
|
|
)
|
|
self._dialect = dialect
|
|
|
|
# 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 defined 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: list[dict[str, bool]] = [{}]
|
|
# Cache the token already checked for typedef-name status to avoid repeated
|
|
# lookups when peeking or consuming. Cleared when scopes or bindings change.
|
|
self._typedef_checked_token: Token | None = None
|
|
self._tokens: _TokenStream = _TokenStream(self.clex.token)
|
|
|
|
def parse(
|
|
self, text: str, filename: str = "", debug: bool = False
|
|
) -> c_ast.FileAST:
|
|
"""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)
|
|
|
|
debug:
|
|
Deprecated debug flag (unused); for backwards compatibility.
|
|
"""
|
|
self._scope_stack = [{}]
|
|
self._typedef_checked_token = None
|
|
self.clex.input(text, filename)
|
|
self._tokens = _TokenStream(self.clex.token)
|
|
|
|
ast = self._parse_translation_unit_or_empty()
|
|
tok = self._peek()
|
|
if tok is not None:
|
|
self._parse_error(f"before: {tok.value}", self._tok_coord(tok))
|
|
return ast
|
|
|
|
# ------------------------------------------------------------------
|
|
# Scope and declaration helpers
|
|
# ------------------------------------------------------------------
|
|
def _parse_error(self, msg: str, coord: Coord | str | None) -> NoReturn:
|
|
raise ParseError(f"{coord}: {msg}")
|
|
|
|
def _push_scope(self) -> None:
|
|
self._scope_stack.append({})
|
|
self._typedef_checked_token = None
|
|
|
|
def _pop_scope(self) -> None:
|
|
assert len(self._scope_stack) > 1, "Cannot pop the global scope"
|
|
self._scope_stack.pop()
|
|
self._typedef_checked_token = None
|
|
|
|
def _add_typedef_name(self, name: str, coord: Coord | None) -> None:
|
|
"""Add a new typedef name (ie a TYPEID) to the current scope"""
|
|
if not self._scope_stack[-1].get(name, True):
|
|
self._parse_error(
|
|
f"Typedef {name!r} previously declared as non-typedef in this scope",
|
|
coord,
|
|
)
|
|
self._scope_stack[-1][name] = True
|
|
self._typedef_checked_token = None
|
|
|
|
def _add_identifier(self, name: str, coord: Coord | None) -> None:
|
|
"""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(
|
|
f"Non-typedef {name!r} previously declared as typedef in this scope",
|
|
coord,
|
|
)
|
|
self._scope_stack[-1][name] = False
|
|
self._typedef_checked_token = None
|
|
|
|
def _is_type_in_scope(self, name: str) -> bool:
|
|
"""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.
|
|
if name in scope:
|
|
return scope[name]
|
|
return False
|
|
|
|
def _lex_error_func(self, msg: str, line: int, column: int) -> None:
|
|
self._parse_error(msg, Coord(file=self.clex.filename, line=line, column=column))
|
|
|
|
# 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: Any, modifier: Any) -> c_ast.Node:
|
|
"""Tacks a type modifier on a declarator, and returns
|
|
the modified declarator.
|
|
|
|
Note: the declarator and modifier may be modified
|
|
"""
|
|
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: c_ast.Decl | c_ast.Typedef | c_ast.Typename,
|
|
typename: list[c_ast.Node],
|
|
) -> c_ast.Decl | c_ast.Typedef | c_ast.Typename:
|
|
"""Fixes a declaration. Modifies decl."""
|
|
# Reach the underlying basic type
|
|
typ = decl
|
|
while not isinstance(typ, c_ast.TypeDecl):
|
|
typ = typ.type
|
|
|
|
decl.name = typ.declname
|
|
typ.quals = decl.quals[:]
|
|
|
|
# The typename is a list of types. Except for _BitInt with an
|
|
# optional signedness, any type that isn't an IdentifierType 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.
|
|
names: list[str] = []
|
|
for tn in typename:
|
|
if isinstance(tn, c_ast.BitIntType):
|
|
signedness = None
|
|
for spec in typename:
|
|
if spec is tn:
|
|
continue
|
|
if (
|
|
signedness is not None
|
|
or not isinstance(spec, c_ast.IdentifierType)
|
|
or spec.names not in (["signed"], ["unsigned"])
|
|
):
|
|
self._parse_error(
|
|
"Invalid multiple types specified", spec.coord
|
|
)
|
|
signedness = spec.names[0]
|
|
# Specifiers are shared by comma-separated declarations;
|
|
# keep the parsed specifier unchanged when attaching the sign.
|
|
typ.type = c_ast.BitIntType(tn.width, signedness, tn.coord)
|
|
return decl
|
|
|
|
if not isinstance(tn, c_ast.IdentifierType):
|
|
if len(typename) > 1:
|
|
self._parse_error("Invalid multiple types specified", tn.coord)
|
|
else:
|
|
typ.type = tn
|
|
# Normalize atomic specifiers for declarations and type
|
|
# names alike (casts, sizeof, unnamed parameters, etc.).
|
|
return fix_atomic_specifiers(decl)
|
|
names.extend(tn.names)
|
|
|
|
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)
|
|
typ.type = c_ast.IdentifierType(["int"], coord=decl.coord)
|
|
else:
|
|
typ.type = c_ast.IdentifierType(names, coord=typename[0].coord)
|
|
return decl
|
|
|
|
def _add_declaration_specifier(
|
|
self,
|
|
declspec: Optional["_DeclSpec"],
|
|
newspec: str | c_ast.Node,
|
|
kind: "_DeclSpecKind",
|
|
) -> "_DeclSpec":
|
|
"""Append a specifier in source order; see _DeclSpec for the layout."""
|
|
if declspec is None:
|
|
spec: _DeclSpec = {
|
|
"qual": [],
|
|
"storage": [],
|
|
"type": [],
|
|
"function": [],
|
|
"alignment": [],
|
|
"type_attributes": [],
|
|
}
|
|
else:
|
|
spec = declspec
|
|
|
|
if kind == "type":
|
|
assert isinstance(newspec, c_ast.Node)
|
|
spec[kind].append(newspec)
|
|
elif kind == "alignment":
|
|
assert isinstance(newspec, c_ast.Alignas)
|
|
spec[kind].append(newspec)
|
|
else:
|
|
assert isinstance(newspec, str)
|
|
spec[kind].append(newspec)
|
|
return spec
|
|
|
|
def _build_type_name(
|
|
self, spec: "_DeclSpec", decl: c_ast.Node | None, coord: Coord | None
|
|
) -> c_ast.Typename:
|
|
"""Build and normalize an abstract type, including its attributes.
|
|
|
|
Callers select the coordinate according to their grammar context.
|
|
The name, if any, is taken from the declarator by _fix_decl_name_type.
|
|
"""
|
|
typename = c_ast.Typename(
|
|
name=None,
|
|
quals=spec["qual"][:],
|
|
align=None,
|
|
type=decl or c_ast.TypeDecl(None, None, None, None),
|
|
coord=coord,
|
|
)
|
|
return cast(
|
|
c_ast.Typename,
|
|
self._attach_type_attributes(
|
|
self._fix_decl_name_type(typename, spec["type"]), spec
|
|
),
|
|
)
|
|
|
|
def _build_declarations(
|
|
self,
|
|
spec: "_DeclSpec",
|
|
decls: list["_DeclInfo"],
|
|
typedef_namespace: bool = False,
|
|
) -> list[c_ast.Node]:
|
|
"""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 None:
|
|
# 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.
|
|
if decls[0]["decl"] is None:
|
|
if (
|
|
len(spec["type"]) < 2
|
|
or not isinstance(spec["type"][-1], c_ast.IdentifierType)
|
|
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,
|
|
align=spec["alignment"],
|
|
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 isinstance(decls[0]["decl"], _DECLARATOR_NODES):
|
|
decls_0_tail = cast(Any, 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:
|
|
type_name = cast(c_ast.IdentifierType, spec["type"][-1])
|
|
decls_0_tail.declname = type_name.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"],
|
|
align=spec["alignment"],
|
|
storage=spec["storage"],
|
|
funcspec=spec["function"],
|
|
type=decl["decl"],
|
|
init=decl.get("init"),
|
|
bitsize=decl.get("bitsize"),
|
|
coord=decl["decl"].coord,
|
|
)
|
|
|
|
if not isinstance(declaration.type, _DECLARATOR_NODES):
|
|
fixed_decl = declaration
|
|
else:
|
|
fixed_decl = self._fix_decl_name_type(declaration, spec["type"])
|
|
|
|
# Record the declared name in the current scope so the parser can
|
|
# distinguish identifiers from typedef names.
|
|
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(self._attach_type_attributes(fixed_decl, spec))
|
|
|
|
return declarations
|
|
|
|
def _build_function_definition(
|
|
self,
|
|
spec: "_DeclSpec",
|
|
decl: c_ast.Node,
|
|
param_decls: list[c_ast.Node] | None,
|
|
body: c_ast.Node,
|
|
) -> c_ast.Node:
|
|
"""Builds a function definition."""
|
|
if "typedef" in spec["storage"]:
|
|
self._parse_error("Invalid typedef", decl.coord)
|
|
|
|
declaration = self._build_declarations(
|
|
spec=spec,
|
|
decls=[{"decl": decl}],
|
|
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: str) -> type:
|
|
"""Given a token (either STRUCT or UNION), selects the
|
|
appropriate AST class.
|
|
"""
|
|
if token == "struct":
|
|
return c_ast.Struct
|
|
else:
|
|
return c_ast.Union
|
|
|
|
# ------------------------------------------------------------------
|
|
# Token helpers
|
|
# ------------------------------------------------------------------
|
|
def _peek(self, k: int = 1) -> Token | None:
|
|
"""Return the k-th next token without consuming it (1-based)."""
|
|
tok = self._tokens.peek(k)
|
|
if tok is not self._typedef_checked_token:
|
|
self._classify_identifier(tok)
|
|
return tok
|
|
|
|
def _classify_identifier(self, tok: Token | None) -> None:
|
|
# Buffered identifiers are interpreted in the current scope, including
|
|
# after backtracking. Peeking and consuming can reuse the classification
|
|
# until a scope or binding changes.
|
|
if tok is not None and tok.type in {"ID", "TYPEID"}:
|
|
tok.type = "TYPEID" if self._is_type_in_scope(tok.value) else "ID"
|
|
self._typedef_checked_token = tok
|
|
|
|
def _peek_type(self, k: int = 1) -> str | None:
|
|
"""Return the type of the k-th next token, or None if absent (1-based)."""
|
|
tok = self._peek(k)
|
|
return tok.type if tok is not None else None
|
|
|
|
def _advance(self) -> Token:
|
|
tok = self._tokens.next()
|
|
if tok is None:
|
|
self._parse_error("At end of input", self.clex.filename)
|
|
if tok is not self._typedef_checked_token:
|
|
self._classify_identifier(tok)
|
|
return tok
|
|
|
|
def _accept(self, token_type: str) -> Token | None:
|
|
"""Conditionally consume next token, only if it's of token_type.
|
|
|
|
If it is of the expected type, consume and return it.
|
|
Otherwise, leaves the token intact and returns None.
|
|
"""
|
|
tok = self._peek()
|
|
if tok is not None and tok.type == token_type:
|
|
return self._advance()
|
|
return None
|
|
|
|
def _expect(self, token_type: str) -> Token:
|
|
tok = self._advance()
|
|
if tok.type != token_type:
|
|
self._parse_error(f"before: {tok.value}", self._tok_coord(tok))
|
|
return tok
|
|
|
|
def _mark(self) -> int:
|
|
return self._tokens.mark()
|
|
|
|
def _reset(self, mark: int) -> None:
|
|
self._tokens.reset(mark)
|
|
|
|
def _tok_coord(self, tok: Token) -> Coord:
|
|
return Coord(file=tok.filename, line=tok.lineno, column=tok.column)
|
|
|
|
def _starts_declaration(self, tok: Token | None = None) -> bool:
|
|
tok = tok or self._peek()
|
|
if tok is None:
|
|
return False
|
|
return tok.type in _DECL_START
|
|
|
|
def _starts_expression(self, tok: Token | None = None) -> bool:
|
|
tok = tok or self._peek()
|
|
if tok is None:
|
|
return False
|
|
return tok.type in _STARTS_EXPRESSION
|
|
|
|
def _starts_statement(self) -> bool:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
mark = self._mark()
|
|
self._parse_attribute_specifier_sequence()
|
|
starts = not self._starts_declaration()
|
|
self._reset(mark)
|
|
return starts
|
|
tok_type = self._peek_type()
|
|
if tok_type is None:
|
|
return False
|
|
if tok_type in _STARTS_STATEMENT:
|
|
return True
|
|
return self._starts_expression()
|
|
|
|
def _starts_declarator(self, id_only: bool = False) -> bool:
|
|
tok_type = self._peek_type()
|
|
if tok_type is None:
|
|
return False
|
|
if tok_type in {"TIMES", "LPAREN"}:
|
|
return True
|
|
if id_only:
|
|
return tok_type == "ID"
|
|
return tok_type in {"ID", "TYPEID"}
|
|
|
|
def _peek_declarator_name_info(self) -> tuple[str | None, bool]:
|
|
mark = self._mark()
|
|
tok_type, saw_paren = self._scan_declarator_name_info()
|
|
self._reset(mark)
|
|
return tok_type, saw_paren
|
|
|
|
def _parse_any_declarator(
|
|
self, allow_abstract: bool = False, typeid_paren_as_abstract: bool = False
|
|
) -> tuple[c_ast.Node | None, bool]:
|
|
# C declarators are ambiguous without lookahead. For example:
|
|
# int foo(int (aa)); -> aa is a name (ID)
|
|
# typedef char TT;
|
|
# int bar(int (TT)); -> TT is a type (TYPEID) in parens
|
|
name_type, saw_paren = self._peek_declarator_name_info()
|
|
if name_type is None or (
|
|
typeid_paren_as_abstract and name_type == "TYPEID" and saw_paren
|
|
):
|
|
if not allow_abstract:
|
|
tok = self._peek()
|
|
coord = self._tok_coord(tok) if tok is not None else self.clex.filename
|
|
self._parse_error("Invalid declarator", coord)
|
|
decl = self._parse_abstract_declarator_opt()
|
|
return decl, False
|
|
|
|
if name_type == "TYPEID":
|
|
if typeid_paren_as_abstract:
|
|
decl = self._parse_typeid_noparen_declarator()
|
|
else:
|
|
decl = self._parse_typeid_declarator()
|
|
else:
|
|
decl = self._parse_id_declarator()
|
|
return decl, True
|
|
|
|
def _scan_declarator_name_info(self) -> tuple[str | None, bool]:
|
|
saw_paren = False
|
|
while self._accept("TIMES"):
|
|
self._parse_attribute_specifier_sequence()
|
|
while self._peek_type() in _TYPE_QUALIFIER:
|
|
self._advance()
|
|
|
|
tok = self._peek()
|
|
if tok is None:
|
|
return None, saw_paren
|
|
if tok.type in {"ID", "TYPEID"}:
|
|
self._advance()
|
|
return tok.type, saw_paren
|
|
if tok.type == "LPAREN":
|
|
saw_paren = True
|
|
self._advance()
|
|
tok_type, nested_paren = self._scan_declarator_name_info()
|
|
if nested_paren:
|
|
saw_paren = True
|
|
depth = 1
|
|
while True:
|
|
tok = self._peek()
|
|
if tok is None:
|
|
return None, saw_paren
|
|
if tok.type == "LPAREN":
|
|
depth += 1
|
|
elif tok.type == "RPAREN":
|
|
depth -= 1
|
|
self._advance()
|
|
if depth == 0:
|
|
break
|
|
continue
|
|
self._advance()
|
|
return tok_type, saw_paren
|
|
return None, saw_paren
|
|
|
|
def _starts_direct_abstract_declarator(self) -> bool:
|
|
return self._peek_type() in {"LPAREN", "LBRACKET"}
|
|
|
|
def _is_assignment_op(self) -> bool:
|
|
tok = self._peek()
|
|
return tok is not None and tok.type in _ASSIGNMENT_OPS
|
|
|
|
def _try_parse_paren_type_name(
|
|
self,
|
|
) -> tuple[c_ast.Typename, int, Token, list[str]] | None:
|
|
"""Parse and return a parenthesized type name if present.
|
|
|
|
Returns (typ, mark, lparen_tok, storage) when the next tokens look like
|
|
'(' type_name ')', where typ is the parsed type name, mark is the
|
|
token-stream position before parsing, and lparen_tok is the LPAREN
|
|
token. C23 storage specifiers may precede the type name, but require a
|
|
following '{' for a compound literal. Returns None if no parenthesized
|
|
type name is present.
|
|
"""
|
|
lparen_tok = self._peek()
|
|
# Avoid saving a backtracking position when there's no '(' to parse.
|
|
if lparen_tok is None or lparen_tok.type != "LPAREN":
|
|
return None
|
|
mark = self._mark()
|
|
self._advance()
|
|
if not self._starts_declaration():
|
|
self._reset(mark)
|
|
return None
|
|
storage = []
|
|
while self._peek_type() in _STORAGE_CLASS:
|
|
tok = self._advance()
|
|
if self._dialect != "c23":
|
|
self._parse_error(
|
|
"Storage class specifiers in compound literals require C23",
|
|
self._tok_coord(tok),
|
|
)
|
|
storage.append(tok.value)
|
|
typ = self._parse_type_name()
|
|
if self._accept("RPAREN") is None:
|
|
self._reset(mark)
|
|
return None
|
|
if storage and self._peek_type() != "LBRACE":
|
|
self._parse_error(
|
|
"Storage class specifiers require a compound literal initializer",
|
|
self._tok_coord(lparen_tok),
|
|
)
|
|
return typ, mark, lparen_tok, storage
|
|
|
|
# ------------------------------------------------------------------
|
|
# Top-level
|
|
# ------------------------------------------------------------------
|
|
def _parse_attribute_specifier_sequence(self) -> list[c_ast.AttributeSpecifier]:
|
|
groups = []
|
|
while self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
opening = self._advance()
|
|
if self._dialect != "c23":
|
|
self._parse_error("Attributes require C23", self._tok_coord(opening))
|
|
attributes = []
|
|
while self._peek_type() != "ATTRIBUTE_END":
|
|
if self._accept("COMMA"):
|
|
continue
|
|
name = self._parse_identifier_or_typeid()
|
|
namespace = None
|
|
if self._accept("COLONCOLON"):
|
|
namespace = name.name
|
|
name = self._parse_identifier_or_typeid()
|
|
arguments = self._accept("ATTRIBUTE_ARGUMENTS")
|
|
attributes.append(
|
|
c_ast.Attribute(
|
|
name.name,
|
|
namespace,
|
|
arguments.value if arguments is not None else None,
|
|
name.coord,
|
|
)
|
|
)
|
|
if not self._accept("COMMA"):
|
|
break
|
|
self._expect("ATTRIBUTE_END")
|
|
groups.append(
|
|
c_ast.AttributeSpecifier(attributes, self._tok_coord(opening))
|
|
)
|
|
return groups
|
|
|
|
def _attribute_declarations(
|
|
self, nodes: list[c_ast.Node], attributes: list[c_ast.AttributeSpecifier]
|
|
) -> list[c_ast.Node]:
|
|
"""Attach declaration prefixes, shared by comma-separated declarators."""
|
|
if not nodes or isinstance(nodes[0], c_ast.Pragma):
|
|
self._parse_error(
|
|
"Expected declaration after attributes", attributes[0].coord
|
|
)
|
|
for node in nodes:
|
|
# Trailing pragmas belong to the surrounding list, not to the
|
|
# declaration carrying these attributes.
|
|
if isinstance(node, c_ast.Pragma):
|
|
continue
|
|
decl = node.decl if isinstance(node, c_ast.FuncDef) else node
|
|
if not isinstance(decl, (c_ast.Decl, c_ast.Typedef)):
|
|
self._parse_error(
|
|
"Expected declaration after attributes", attributes[0].coord
|
|
)
|
|
decl.attributes = attributes
|
|
return nodes
|
|
|
|
def _attribute_statement(
|
|
self, stmt: c_ast.Node, attributes: list[c_ast.AttributeSpecifier]
|
|
) -> c_ast.Node:
|
|
if isinstance(stmt, (c_ast.Label, c_ast.Case, c_ast.Default)):
|
|
stmt.attributes = attributes
|
|
return stmt
|
|
return c_ast.AttributedStatement(attributes, stmt, attributes[0].coord)
|
|
|
|
def _attach_type_attributes(
|
|
self, decl: c_ast.Decl | c_ast.Typedef | c_ast.Typename, spec: "_DeclSpec"
|
|
) -> c_ast.Decl | c_ast.Typedef | c_ast.Typename:
|
|
"""Keep attributes after the base type, inside pointer/array modifiers."""
|
|
if spec["type_attributes"]:
|
|
target = decl
|
|
while isinstance(
|
|
target.type,
|
|
(c_ast.PtrDecl, c_ast.ArrayDecl, c_ast.FuncDecl, c_ast.TypeDecl),
|
|
):
|
|
target = target.type
|
|
target.type = c_ast.AttributedType(
|
|
target.type, spec["type_attributes"], target.type.coord
|
|
)
|
|
return decl
|
|
|
|
# BNF: translation_unit_or_empty : translation_unit | empty
|
|
def _parse_translation_unit_or_empty(self) -> c_ast.FileAST:
|
|
if self._peek() is None:
|
|
return c_ast.FileAST([])
|
|
return c_ast.FileAST(self._parse_translation_unit())
|
|
|
|
# BNF: translation_unit : external_declaration+
|
|
def _parse_translation_unit(self) -> list[c_ast.Node]:
|
|
ext = []
|
|
while self._peek() is not None:
|
|
ext.extend(self._parse_external_declaration())
|
|
return ext
|
|
|
|
# BNF: external_declaration : function_definition
|
|
# | declaration
|
|
# | pp_directive
|
|
# | pppragma_directive
|
|
# | static_assert
|
|
# | ';'
|
|
def _parse_external_declaration(self) -> list[c_ast.Node]:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
if self._accept("SEMI"):
|
|
return [c_ast.AttributeDeclaration(attributes, attributes[0].coord)]
|
|
return self._attribute_declarations(
|
|
self._parse_external_declaration(), attributes
|
|
)
|
|
tok = self._peek()
|
|
if tok is None:
|
|
return []
|
|
if tok.type == "PPHASH":
|
|
self._parse_pp_directive()
|
|
return []
|
|
if tok.type in {"PPPRAGMA", "_PRAGMA"}:
|
|
return [self._parse_pppragma_directive()]
|
|
if self._accept("SEMI"):
|
|
return []
|
|
if tok.type == "_STATIC_ASSERT":
|
|
return [self._parse_static_assert()]
|
|
|
|
if not self._starts_declaration(tok):
|
|
# Special handling for old-style function definitions that have an
|
|
# implicit return type, e.g.
|
|
#
|
|
# foo() {
|
|
# return 5;
|
|
# }
|
|
#
|
|
# These get an implicit 'int' return type.
|
|
decl = self._parse_id_declarator()
|
|
if self._peek_type() != "LBRACE":
|
|
self._parse_error("Invalid function definition", decl.coord)
|
|
spec: _DeclSpec = {
|
|
"qual": [],
|
|
"alignment": [],
|
|
"storage": [],
|
|
"type": [c_ast.IdentifierType(["int"], coord=decl.coord)],
|
|
"function": [],
|
|
"type_attributes": [],
|
|
}
|
|
return [self._parse_function_definition(spec, decl)]
|
|
|
|
# From here on, parsing a standard declatation/definition.
|
|
spec, saw_type, spec_coord = self._parse_declaration_specifiers(
|
|
allow_no_type=True
|
|
)
|
|
if self._is_inferred_auto(spec):
|
|
decls = self._parse_auto_declaration(spec)
|
|
return self._parse_declaration_end(decls)
|
|
|
|
name_type, _ = self._peek_declarator_name_info()
|
|
if name_type != "ID":
|
|
decls = self._parse_decl_body_with_spec(spec, saw_type)
|
|
return self._parse_declaration_end(decls)
|
|
|
|
decl = self._parse_id_declarator()
|
|
|
|
if self._peek_type() == "LBRACE" or self._starts_declaration():
|
|
if not spec["type"]:
|
|
spec["type"] = [c_ast.IdentifierType(["int"], coord=spec_coord)]
|
|
return [self._parse_function_definition(spec, decl)]
|
|
|
|
decl_dict: _DeclInfo = {"decl": decl}
|
|
if self._accept("EQUALS"):
|
|
decl_dict["init"] = self._parse_initializer()
|
|
decls = self._parse_init_declarator_list(first=decl_dict)
|
|
decls = self._build_declarations(spec=spec, decls=decls, typedef_namespace=True)
|
|
return self._parse_declaration_end(decls)
|
|
|
|
def _parse_function_definition(
|
|
self, spec: "_DeclSpec", decl: c_ast.Node
|
|
) -> c_ast.Node:
|
|
# Open the scope shared by parameters, old-style parameter declarations,
|
|
# and the outermost body.
|
|
self._push_scope()
|
|
if isinstance(decl, c_ast.FuncDecl) and decl.args is not None:
|
|
for param in decl.args.params:
|
|
name = getattr(param, "name", None)
|
|
if name:
|
|
self._add_identifier(name, param.coord)
|
|
param_decls = (
|
|
self._parse_declaration_list() if self._starts_declaration() else None
|
|
)
|
|
if self._peek_type() != "LBRACE":
|
|
self._parse_error("Invalid function definition", decl.coord)
|
|
body = self._parse_compound_statement(new_scope=False)
|
|
self._pop_scope()
|
|
return self._build_function_definition(spec, decl, param_decls, body)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Declarations
|
|
#
|
|
# Declarations always come as lists (because they can be several in one
|
|
# line). When returning parsed declarations, a list is always returned -
|
|
# even if it contains a single element. Pragmas before the final semicolon
|
|
# follow the declarations in that list.
|
|
# ------------------------------------------------------------------
|
|
# BNF: declaration : decl_body pppragma_directive* ';' | static_assert
|
|
def _parse_declaration(self) -> list[c_ast.Node]:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
if self._accept("SEMI"):
|
|
return [c_ast.AttributeDeclaration(attributes, attributes[0].coord)]
|
|
return self._attribute_declarations(self._parse_declaration(), attributes)
|
|
if self._peek_type() == "_STATIC_ASSERT":
|
|
return [self._parse_static_assert()]
|
|
decls = self._parse_decl_body()
|
|
return self._parse_declaration_end(decls)
|
|
|
|
def _parse_declaration_end(self, decls: list[c_ast.Node]) -> list[c_ast.Node]:
|
|
"""Consume a declaration's semicolon and retain preceding pragmas.
|
|
|
|
Macro expansions can place diagnostic pragmas just before the final
|
|
semicolon (issue #478). Represent them after the completed declarations
|
|
in the surrounding list, just like pragmas following the semicolon.
|
|
"""
|
|
pragmas = self._parse_pppragma_directive_list()
|
|
self._expect("SEMI")
|
|
return decls + pragmas
|
|
|
|
# BNF: decl_body : declaration_specifiers decl_body_with_spec
|
|
def _parse_decl_body(self) -> list[c_ast.Node]:
|
|
spec, saw_type, _ = self._parse_declaration_specifiers(allow_no_type=True)
|
|
return self._parse_decl_body_with_spec(spec, saw_type)
|
|
|
|
# BNF: decl_body_with_spec : init_declarator_list
|
|
# | struct_or_union_or_enum_only
|
|
def _parse_decl_body_with_spec(
|
|
self, spec: "_DeclSpec", saw_type: bool
|
|
) -> list[c_ast.Node]:
|
|
if self._is_inferred_auto(spec):
|
|
return self._parse_auto_declaration(spec)
|
|
|
|
# saw_type is True if the specifiers included an actual type (as
|
|
# opposed to only storage/function/qualifiers).
|
|
decl_infos: list[_DeclInfo] | None = None
|
|
if saw_type:
|
|
if self._starts_declarator():
|
|
decl_infos = self._parse_init_declarator_list()
|
|
else:
|
|
if self._starts_declarator(id_only=True):
|
|
decl_infos = self._parse_init_declarator_list(id_only=True)
|
|
|
|
decls: list[c_ast.Node]
|
|
if decl_infos is None:
|
|
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):
|
|
decl = c_ast.Decl(
|
|
name=None,
|
|
quals=spec["qual"],
|
|
align=spec["alignment"],
|
|
storage=spec["storage"],
|
|
funcspec=spec["function"],
|
|
type=ty[0],
|
|
init=None,
|
|
bitsize=None,
|
|
coord=ty[0].coord,
|
|
)
|
|
decls = [self._attach_type_attributes(decl, spec)]
|
|
else:
|
|
decls = self._build_declarations(
|
|
spec=spec,
|
|
decls=[{"decl": None}],
|
|
typedef_namespace=True,
|
|
)
|
|
else:
|
|
decls = self._build_declarations(
|
|
spec=spec, decls=decl_infos, typedef_namespace=True
|
|
)
|
|
|
|
return decls
|
|
|
|
def _is_inferred_auto(self, spec: "_DeclSpec") -> bool:
|
|
return self._dialect == "c23" and "auto" in spec["storage"] and not spec["type"]
|
|
|
|
# C23 type inference uses one plain (possibly parenthesized) name and an
|
|
# expression initializer. Preserve its syntax without resolving the type.
|
|
def _parse_auto_declaration(self, spec: "_DeclSpec") -> list[c_ast.Node]:
|
|
decl = self._parse_id_declarator()
|
|
if not isinstance(decl, c_ast.TypeDecl):
|
|
self._parse_error(
|
|
"Inferred auto declaration requires a plain identifier", decl.coord
|
|
)
|
|
self._expect("EQUALS")
|
|
init = self._parse_assignment_expression()
|
|
spec["type"] = [c_ast.InferredType(coord=decl.coord)]
|
|
# Register the name only after parsing the initializer.
|
|
return self._build_declarations(
|
|
spec=spec,
|
|
decls=[{"decl": decl, "init": init}],
|
|
typedef_namespace=True,
|
|
)
|
|
|
|
# BNF: declaration_list : (declaration pppragma_directive*)+
|
|
def _parse_declaration_list(self) -> list[c_ast.Node]:
|
|
decls = []
|
|
while self._starts_declaration():
|
|
decls.extend(self._parse_declaration())
|
|
# K&R parameter declarations retain pragmas on either side of
|
|
# their semicolons, including when reparsing generated code.
|
|
decls.extend(self._parse_pppragma_directive_list())
|
|
return decls
|
|
|
|
# BNF: declaration_specifiers : (storage_class_specifier
|
|
# | type_specifier
|
|
# | type_qualifier
|
|
# | function_specifier
|
|
# | alignment_specifier)+
|
|
def _parse_declaration_specifiers(
|
|
self, allow_no_type: bool = False
|
|
) -> tuple["_DeclSpec", bool, Coord | None]:
|
|
"""Parse declaration-specifier sequence.
|
|
|
|
allow_no_type:
|
|
If True, allow a missing type specifier without error.
|
|
|
|
Returns:
|
|
(spec, saw_type, first_coord) where spec is a dict with
|
|
qual/storage/type/function/alignment/type_attributes entries, saw_type is True
|
|
if a type specifier was consumed, and first_coord is the coord
|
|
of the first specifier token (used for diagnostics).
|
|
"""
|
|
spec, saw_type, first_coord = self._parse_specifiers(
|
|
allow_storage_and_function=True
|
|
)
|
|
if spec is None:
|
|
self._parse_error("Invalid declaration", self.clex.filename)
|
|
if not saw_type and not allow_no_type:
|
|
self._parse_error("Missing type in declaration", first_coord)
|
|
return spec, saw_type, first_coord
|
|
|
|
# BNF: specifier_qualifier_list : (type_specifier
|
|
# | type_qualifier
|
|
# | alignment_specifier)+
|
|
def _parse_specifier_qualifier_list(self) -> "_DeclSpec":
|
|
spec, saw_type, first_coord = self._parse_specifiers(
|
|
allow_storage_and_function=False
|
|
)
|
|
if spec is None:
|
|
self._parse_error("Invalid specifier list", self.clex.filename)
|
|
if not saw_type and not spec["alignment"]:
|
|
self._parse_error("Missing type in declaration", first_coord)
|
|
return spec
|
|
|
|
def _parse_specifiers(
|
|
self, *, allow_storage_and_function: bool
|
|
) -> tuple["_DeclSpec | None", bool, Coord | None]:
|
|
"""Consume specifiers shared by declarations, members, and type names.
|
|
|
|
Storage classes and function specifiers are only accepted in
|
|
declarations. Callers handle their own missing-specifier diagnostics.
|
|
"""
|
|
spec = None
|
|
saw_type = False
|
|
first_coord = None
|
|
|
|
while True:
|
|
tok = self._peek()
|
|
if tok is None:
|
|
break
|
|
|
|
if tok.type == "ATTRIBUTE_BEGIN" and spec is not None:
|
|
spec["type_attributes"] = self._parse_attribute_specifier_sequence()
|
|
break
|
|
|
|
# Once a type has been seen, a typedef name can be the declarator
|
|
# (e.g. the second T in "T T"), rather than another type specifier.
|
|
if tok.type == "TYPEID" and saw_type:
|
|
break
|
|
|
|
# _Atomic is a type specifier with parentheses, a qualifier without.
|
|
is_atomic_specifier = (
|
|
tok.type == "_ATOMIC" and self._peek_type(2) == "LPAREN"
|
|
)
|
|
if first_coord is None:
|
|
first_coord = self._tok_coord(tok)
|
|
|
|
kind: _DeclSpecKind = "type"
|
|
newspec: str | c_ast.Node
|
|
if tok.type == "_ALIGNAS":
|
|
kind = "alignment"
|
|
newspec = self._parse_alignment_specifier()
|
|
elif is_atomic_specifier:
|
|
newspec = self._parse_atomic_specifier()
|
|
elif tok.type in _TYPEOF_SPEC:
|
|
newspec = self._parse_typeof_specifier()
|
|
elif tok.type == "_BITINT":
|
|
newspec = self._parse_bitint_specifier()
|
|
elif tok.type in _TYPE_QUALIFIER:
|
|
kind = "qual"
|
|
newspec = self._advance().value
|
|
elif allow_storage_and_function and tok.type in _STORAGE_CLASS:
|
|
kind = "storage"
|
|
newspec = self._advance().value
|
|
elif allow_storage_and_function and tok.type in _FUNCTION_SPEC:
|
|
kind = "function"
|
|
newspec = self._advance().value
|
|
elif tok.type in _TYPE_SPEC_SIMPLE or tok.type == "TYPEID":
|
|
tok = self._advance()
|
|
newspec = c_ast.IdentifierType([tok.value], coord=self._tok_coord(tok))
|
|
elif tok.type in {"STRUCT", "UNION"}:
|
|
newspec = self._parse_struct_or_union_specifier()
|
|
elif tok.type == "ENUM":
|
|
newspec = self._parse_enum_specifier()
|
|
else:
|
|
break
|
|
|
|
spec = self._add_declaration_specifier(spec, newspec, kind)
|
|
if kind == "type":
|
|
saw_type = True
|
|
|
|
return spec, saw_type, first_coord
|
|
|
|
# BNF: type_qualifier_list : type_qualifier+
|
|
def _parse_type_qualifier_list(self) -> list[str]:
|
|
quals = []
|
|
while self._peek_type() in _TYPE_QUALIFIER:
|
|
quals.append(self._advance().value)
|
|
return quals
|
|
|
|
# BNF: alignment_specifier : _ALIGNAS '(' type_name | constant_expression ')'
|
|
def _parse_alignment_specifier(self) -> c_ast.Alignas:
|
|
tok = self._expect("_ALIGNAS")
|
|
self._expect("LPAREN")
|
|
|
|
if self._starts_declaration():
|
|
typ = self._parse_type_name()
|
|
self._expect("RPAREN")
|
|
return c_ast.Alignas(typ, self._tok_coord(tok))
|
|
|
|
expr = self._parse_constant_expression()
|
|
self._expect("RPAREN")
|
|
return c_ast.Alignas(expr, self._tok_coord(tok))
|
|
|
|
# BNF: atomic_specifier : _ATOMIC '(' type_name ')'
|
|
def _parse_atomic_specifier(self) -> c_ast.Node:
|
|
self._expect("_ATOMIC")
|
|
self._expect("LPAREN")
|
|
typ = self._parse_type_name()
|
|
self._expect("RPAREN")
|
|
typ.quals.append("_Atomic")
|
|
return typ
|
|
|
|
# BNF: typeof_specifier : (TYPEOF | TYPEOF_UNQUAL) '(' (type_name | expression) ')'
|
|
def _parse_typeof_specifier(self) -> c_ast.TypeOf:
|
|
tok = self._advance()
|
|
self._expect("LPAREN")
|
|
if self._starts_declaration():
|
|
expr = self._parse_type_name()
|
|
else:
|
|
expr = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
return c_ast.TypeOf(tok.value, expr, self._tok_coord(tok))
|
|
|
|
# BNF: bitint_specifier : _BITINT '(' constant_expression ')'
|
|
def _parse_bitint_specifier(self) -> c_ast.BitIntType:
|
|
tok = self._expect("_BITINT")
|
|
self._expect("LPAREN")
|
|
width = self._parse_constant_expression()
|
|
self._expect("RPAREN")
|
|
return c_ast.BitIntType(width, None, self._tok_coord(tok))
|
|
|
|
# BNF: init_declarator_list : init_declarator (',' init_declarator)*
|
|
def _parse_init_declarator_list(
|
|
self, first: Optional["_DeclInfo"] = None, id_only: bool = False
|
|
) -> list["_DeclInfo"]:
|
|
decls = (
|
|
[first]
|
|
if first is not None
|
|
else [self._parse_init_declarator(id_only=id_only)]
|
|
)
|
|
|
|
while self._accept("COMMA"):
|
|
decls.append(self._parse_init_declarator(id_only=id_only))
|
|
return decls
|
|
|
|
# BNF: init_declarator : declarator ('=' initializer)?
|
|
def _parse_init_declarator(self, id_only: bool = False) -> "_DeclInfo":
|
|
decl = self._parse_id_declarator() if id_only else self._parse_declarator()
|
|
if self._accept("EQUALS"):
|
|
return {"decl": decl, "init": self._parse_initializer()}
|
|
return {"decl": decl}
|
|
|
|
# ------------------------------------------------------------------
|
|
# Structs/unions/enums
|
|
# ------------------------------------------------------------------
|
|
# BNF: struct_or_union_specifier : struct_or_union ID? '{' struct_declaration_list? '}'
|
|
# | struct_or_union ID
|
|
def _parse_struct_or_union_specifier(self) -> c_ast.Node:
|
|
tok = self._advance()
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
klass = self._select_struct_union_class(tok.value)
|
|
|
|
if self._peek_type() in {"ID", "TYPEID"}:
|
|
name_tok = self._advance()
|
|
if self._peek_type() == "LBRACE":
|
|
self._advance()
|
|
if self._accept("RBRACE"):
|
|
return klass(
|
|
attributes=attributes,
|
|
name=name_tok.value,
|
|
decls=[],
|
|
coord=self._tok_coord(name_tok),
|
|
)
|
|
decls = self._parse_struct_declaration_list()
|
|
self._expect("RBRACE")
|
|
return klass(
|
|
attributes=attributes,
|
|
name=name_tok.value,
|
|
decls=decls,
|
|
coord=self._tok_coord(name_tok),
|
|
)
|
|
|
|
return klass(
|
|
attributes=attributes,
|
|
name=name_tok.value,
|
|
decls=None,
|
|
coord=self._tok_coord(name_tok),
|
|
)
|
|
|
|
if self._peek_type() == "LBRACE":
|
|
brace_tok = self._advance()
|
|
if self._accept("RBRACE"):
|
|
return klass(
|
|
attributes=attributes,
|
|
name=None,
|
|
decls=[],
|
|
coord=self._tok_coord(brace_tok),
|
|
)
|
|
decls = self._parse_struct_declaration_list()
|
|
self._expect("RBRACE")
|
|
return klass(
|
|
attributes=attributes,
|
|
name=None,
|
|
decls=decls,
|
|
coord=self._tok_coord(brace_tok),
|
|
)
|
|
|
|
self._parse_error("Invalid struct/union declaration", self._tok_coord(tok))
|
|
|
|
# BNF: struct_declaration_list : struct_declaration+
|
|
def _parse_struct_declaration_list(self) -> list[c_ast.Node]:
|
|
decls = []
|
|
while self._peek_type() not in {None, "RBRACE"}:
|
|
items = self._parse_struct_declaration()
|
|
if items is None:
|
|
continue
|
|
decls.extend(items)
|
|
return decls
|
|
|
|
# BNF: struct_declaration : specifier_qualifier_list struct_declarator_list?
|
|
# pppragma_directive* ';'
|
|
# | static_assert
|
|
# | pppragma_directive
|
|
def _parse_struct_declaration(self) -> list[c_ast.Node] | None:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
return self._attribute_declarations(
|
|
self._parse_struct_declaration() or [], attributes
|
|
)
|
|
if self._peek_type() == "_STATIC_ASSERT":
|
|
return [self._parse_static_assert()]
|
|
if self._peek_type() == "SEMI":
|
|
self._advance()
|
|
return None
|
|
if self._peek_type() in {"PPPRAGMA", "_PRAGMA"}:
|
|
return [self._parse_pppragma_directive()]
|
|
|
|
spec = self._parse_specifier_qualifier_list()
|
|
assert "typedef" not in spec.get("storage", [])
|
|
|
|
decls = None
|
|
if self._starts_declarator() or self._peek_type() == "COLON":
|
|
decls = self._parse_struct_declarator_list()
|
|
if decls is not None:
|
|
return self._parse_declaration_end(
|
|
self._build_declarations(spec=spec, decls=decls)
|
|
)
|
|
|
|
if len(spec["type"]) == 1:
|
|
node = spec["type"][0]
|
|
if isinstance(node, c_ast.Node):
|
|
decl_type = node
|
|
else:
|
|
decl_type = c_ast.IdentifierType(node)
|
|
return self._parse_declaration_end(
|
|
self._build_declarations(spec=spec, decls=[{"decl": decl_type}])
|
|
)
|
|
|
|
return self._parse_declaration_end(
|
|
self._build_declarations(spec=spec, decls=[{"decl": None}])
|
|
)
|
|
|
|
# BNF: struct_declarator_list : struct_declarator (',' struct_declarator)*
|
|
def _parse_struct_declarator_list(self) -> list["_DeclInfo"]:
|
|
decls = [self._parse_struct_declarator()]
|
|
while self._accept("COMMA"):
|
|
decls.append(self._parse_struct_declarator())
|
|
return decls
|
|
|
|
# BNF: struct_declarator : declarator? ':' constant_expression
|
|
# | declarator (':' constant_expression)?
|
|
def _parse_struct_declarator(self) -> "_DeclInfo":
|
|
if self._accept("COLON"):
|
|
bitsize = self._parse_constant_expression()
|
|
return {
|
|
"decl": c_ast.TypeDecl(None, None, None, None),
|
|
"bitsize": bitsize,
|
|
}
|
|
|
|
decl = self._parse_declarator()
|
|
if self._accept("COLON"):
|
|
bitsize = self._parse_constant_expression()
|
|
return {"decl": decl, "bitsize": bitsize}
|
|
|
|
return {"decl": decl}
|
|
|
|
# BNF: enum_specifier : ENUM ID? enum_type_specifier? '{' enumerator_list '}'
|
|
# | ENUM ID enum_type_specifier?
|
|
def _parse_enum_specifier(self) -> c_ast.Node:
|
|
tok = self._expect("ENUM")
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
name = None
|
|
if self._peek_type() in {"ID", "TYPEID"}:
|
|
name = self._advance().value
|
|
|
|
underlying_type = None
|
|
# A colon followed by an expression belongs to an unnamed bit-field,
|
|
# e.g. struct S { enum E : 3; }, rather than a fixed underlying type.
|
|
if self._peek_type() == "COLON" and self._starts_declaration(self._peek(2)):
|
|
underlying_type = self._parse_enum_type_specifier()
|
|
|
|
enums = None
|
|
if name is None or self._peek_type() == "LBRACE":
|
|
self._expect("LBRACE")
|
|
enums = self._parse_enumerator_list()
|
|
self._expect("RBRACE")
|
|
return c_ast.Enum(
|
|
name,
|
|
enums,
|
|
underlying_type,
|
|
self._tok_coord(tok),
|
|
attributes=attributes,
|
|
)
|
|
|
|
# BNF: enum_type_specifier : ':' specifier_qualifier_list (C23)
|
|
def _parse_enum_type_specifier(self) -> c_ast.Typename:
|
|
colon = self._expect("COLON")
|
|
if self._dialect != "c23":
|
|
self._parse_error(
|
|
"Fixed underlying enum types require C23", self._tok_coord(colon)
|
|
)
|
|
# The base is a specifier-qualifier list, not a full type name: any
|
|
# following pointer, array, or function declarator belongs to the enum.
|
|
spec = self._parse_specifier_qualifier_list()
|
|
coord = spec["type"][0].coord if spec["type"] else self._tok_coord(colon)
|
|
return self._build_type_name(spec, None, coord)
|
|
|
|
# BNF: enumerator_list : pppragma_directive* enumerator pppragma_directive*
|
|
# (',' pppragma_directive* enumerator pppragma_directive*)*
|
|
# (',' pppragma_directive*)?
|
|
def _parse_enumerator_list(self) -> c_ast.Node:
|
|
members = self._parse_pppragma_directive_list()
|
|
# Pragmas do not count as enumerators: even with leading directives,
|
|
# the list must contain at least one enumerator.
|
|
enum = self._parse_enumerator()
|
|
members.append(enum)
|
|
coord = enum.coord
|
|
while True:
|
|
# Directives may appear on either side of a separating comma, or
|
|
# after the last enumerator. They never replace the comma itself.
|
|
members.extend(self._parse_pppragma_directive_list())
|
|
if not self._accept("COMMA"):
|
|
break
|
|
members.extend(self._parse_pppragma_directive_list())
|
|
if self._peek_type() == "RBRACE":
|
|
break
|
|
members.append(self._parse_enumerator())
|
|
return c_ast.EnumeratorList(members, coord)
|
|
|
|
# BNF: enumerator : ID ('=' constant_expression)?
|
|
def _parse_enumerator(self) -> c_ast.Node:
|
|
name_tok = self._expect("ID")
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
if self._accept("EQUALS"):
|
|
value = self._parse_constant_expression()
|
|
else:
|
|
value = None
|
|
enum = c_ast.Enumerator(
|
|
name_tok.value,
|
|
value,
|
|
self._tok_coord(name_tok),
|
|
attributes=attributes,
|
|
)
|
|
self._add_identifier(enum.name, enum.coord)
|
|
return enum
|
|
|
|
# ------------------------------------------------------------------
|
|
# Declarators
|
|
# ------------------------------------------------------------------
|
|
# BNF: declarator : pointer? direct_declarator
|
|
def _parse_declarator(self) -> c_ast.Node:
|
|
decl, _ = self._parse_any_declarator(
|
|
allow_abstract=False, typeid_paren_as_abstract=False
|
|
)
|
|
assert decl is not None
|
|
return decl
|
|
|
|
# BNF: id_declarator : declarator with ID name
|
|
def _parse_id_declarator(self) -> c_ast.Node:
|
|
return self._parse_declarator_kind(kind="id", allow_paren=True)
|
|
|
|
# BNF: typeid_declarator : declarator with TYPEID name
|
|
def _parse_typeid_declarator(self) -> c_ast.Node:
|
|
return self._parse_declarator_kind(kind="typeid", allow_paren=True)
|
|
|
|
# BNF: typeid_noparen_declarator : declarator without parenthesized name
|
|
def _parse_typeid_noparen_declarator(self) -> c_ast.Node:
|
|
return self._parse_declarator_kind(kind="typeid", allow_paren=False)
|
|
|
|
# BNF: declarator_kind : pointer? direct_declarator(kind)
|
|
def _parse_declarator_kind(self, kind: str, allow_paren: bool) -> c_ast.Node:
|
|
ptr = None
|
|
if self._peek_type() == "TIMES":
|
|
ptr = self._parse_pointer()
|
|
direct = self._parse_direct_declarator(kind, allow_paren=allow_paren)
|
|
if ptr is not None:
|
|
return self._type_modify_decl(direct, ptr)
|
|
return direct
|
|
|
|
# BNF: direct_declarator : ID | TYPEID | '(' declarator ')'
|
|
# | direct_declarator '[' ... ']'
|
|
# | direct_declarator '(' ... ')'
|
|
def _parse_direct_declarator(
|
|
self, kind: str, allow_paren: bool = True
|
|
) -> c_ast.Node:
|
|
if allow_paren and self._accept("LPAREN"):
|
|
decl = self._parse_declarator_kind(kind, allow_paren=True)
|
|
self._expect("RPAREN")
|
|
else:
|
|
if kind == "id":
|
|
name_tok = self._expect("ID")
|
|
else:
|
|
name_tok = self._expect("TYPEID")
|
|
decl = c_ast.TypeDecl(
|
|
declname=name_tok.value,
|
|
type=None,
|
|
quals=None,
|
|
align=None,
|
|
coord=self._tok_coord(name_tok),
|
|
)
|
|
decl.attributes = self._parse_attribute_specifier_sequence()
|
|
|
|
return self._parse_decl_suffixes(decl)
|
|
|
|
def _parse_decl_suffixes(self, decl: c_ast.Node) -> c_ast.Node:
|
|
"""Parse a chain of array/function suffixes and attach them to decl."""
|
|
while True:
|
|
if self._peek_type() == "LBRACKET":
|
|
decl = self._type_modify_decl(decl, self._parse_array_decl(decl))
|
|
continue
|
|
if self._peek_type() == "LPAREN":
|
|
func = self._parse_function_decl(decl)
|
|
decl = self._type_modify_decl(decl, func)
|
|
continue
|
|
break
|
|
return decl
|
|
|
|
# BNF: array_decl : '[' array_specifiers? assignment_expression? ']'
|
|
def _parse_array_decl(self, base_decl: c_ast.Node) -> c_ast.Node:
|
|
return self._parse_array_decl_common(base_type=None, coord=base_decl.coord)
|
|
|
|
def _parse_array_decl_common(
|
|
self, base_type: c_ast.Node | None, coord: Coord | None = None
|
|
) -> c_ast.Node:
|
|
"""Parse an array declarator suffix and return an ArrayDecl node.
|
|
|
|
base_type:
|
|
Base declarator node to attach (None for direct-declarator parsing,
|
|
TypeDecl for abstract declarators).
|
|
|
|
coord:
|
|
Coordinate to use for the ArrayDecl. If None, uses the '[' token.
|
|
"""
|
|
lbrack_tok = self._expect("LBRACKET")
|
|
if coord is None:
|
|
coord = self._tok_coord(lbrack_tok)
|
|
|
|
def make_array_decl(dim, dim_quals):
|
|
return c_ast.ArrayDecl(
|
|
type=base_type,
|
|
dim=dim,
|
|
dim_quals=dim_quals,
|
|
coord=coord,
|
|
attributes=self._parse_attribute_specifier_sequence(),
|
|
)
|
|
|
|
if self._accept("STATIC"):
|
|
dim_quals = ["static"] + (self._parse_type_qualifier_list() or [])
|
|
dim = self._parse_assignment_expression()
|
|
self._expect("RBRACKET")
|
|
return make_array_decl(dim, dim_quals)
|
|
|
|
if self._peek_type() in _TYPE_QUALIFIER:
|
|
dim_quals = self._parse_type_qualifier_list() or []
|
|
if self._accept("STATIC"):
|
|
dim_quals = dim_quals + ["static"]
|
|
dim = self._parse_assignment_expression()
|
|
self._expect("RBRACKET")
|
|
return make_array_decl(dim, dim_quals)
|
|
times_tok = self._accept("TIMES")
|
|
if times_tok:
|
|
self._expect("RBRACKET")
|
|
dim = c_ast.ID(times_tok.value, self._tok_coord(times_tok))
|
|
return make_array_decl(dim, dim_quals)
|
|
dim = None
|
|
if self._starts_expression():
|
|
dim = self._parse_assignment_expression()
|
|
self._expect("RBRACKET")
|
|
return make_array_decl(dim, dim_quals)
|
|
|
|
times_tok = self._accept("TIMES")
|
|
if times_tok:
|
|
self._expect("RBRACKET")
|
|
dim = c_ast.ID(times_tok.value, self._tok_coord(times_tok))
|
|
return make_array_decl(dim, [])
|
|
|
|
dim = None
|
|
if self._starts_expression():
|
|
dim = self._parse_assignment_expression()
|
|
self._expect("RBRACKET")
|
|
return make_array_decl(dim, [])
|
|
|
|
# BNF: function_decl : '(' parameter_type_list_opt | identifier_list_opt ')'
|
|
def _parse_function_decl(self, base_decl: c_ast.Node) -> c_ast.Node:
|
|
self._expect("LPAREN")
|
|
if self._accept("RPAREN"):
|
|
args = None
|
|
else:
|
|
args = (
|
|
self._parse_parameter_type_list()
|
|
if self._starts_declaration() or self._peek_type() == "ELLIPSIS"
|
|
else self._parse_identifier_list_opt()
|
|
)
|
|
self._expect("RPAREN")
|
|
|
|
func = c_ast.FuncDecl(
|
|
args=args,
|
|
type=None,
|
|
coord=base_decl.coord,
|
|
attributes=self._parse_attribute_specifier_sequence(),
|
|
)
|
|
|
|
return func
|
|
|
|
# BNF: pointer : '*' type_qualifier_list? pointer?
|
|
def _parse_pointer(self) -> c_ast.Node | None:
|
|
stars = []
|
|
times_tok = self._accept("TIMES")
|
|
while times_tok:
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
quals = self._parse_type_qualifier_list() or []
|
|
stars.append((quals, self._tok_coord(times_tok), attributes))
|
|
times_tok = self._accept("TIMES")
|
|
|
|
if not stars:
|
|
return None
|
|
|
|
ptr = None
|
|
for quals, coord, attributes in stars:
|
|
ptr = c_ast.PtrDecl(
|
|
quals=quals, type=ptr, coord=coord, attributes=attributes
|
|
)
|
|
return ptr
|
|
|
|
# BNF: parameter_type_list : parameter_list (',' ELLIPSIS)?
|
|
# | ELLIPSIS (C23)
|
|
def _parse_parameter_type_list(self) -> c_ast.ParamList:
|
|
# C23 permits a variadic function with no preceding named parameters.
|
|
ell_tok = self._accept("ELLIPSIS")
|
|
if ell_tok:
|
|
coord = self._tok_coord(ell_tok)
|
|
if self._dialect != "c23":
|
|
self._parse_error("Ellipsis-only parameter lists require C23", coord)
|
|
return c_ast.ParamList([c_ast.EllipsisParam(coord)], coord)
|
|
|
|
params = self._parse_parameter_list()
|
|
if self._peek_type() == "COMMA" and self._peek_type(2) == "ELLIPSIS":
|
|
self._advance()
|
|
ell_tok = self._advance()
|
|
params.params.append(c_ast.EllipsisParam(self._tok_coord(ell_tok)))
|
|
return params
|
|
|
|
# BNF: parameter_list : parameter_declaration (',' parameter_declaration)*
|
|
def _parse_parameter_list(self) -> c_ast.ParamList:
|
|
first = self._parse_parameter_declaration()
|
|
params = c_ast.ParamList([first], first.coord)
|
|
while self._peek_type() == "COMMA" and self._peek_type(2) != "ELLIPSIS":
|
|
self._advance()
|
|
params.params.append(self._parse_parameter_declaration())
|
|
return params
|
|
|
|
# BNF: parameter_declaration : declaration_specifiers declarator?
|
|
# | declaration_specifiers abstract_declarator_opt
|
|
def _parse_parameter_declaration(self) -> c_ast.Node:
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
decl = self._parse_parameter_declaration_body()
|
|
assert isinstance(decl, (c_ast.Decl, c_ast.Typename))
|
|
decl.attributes = attributes
|
|
return decl
|
|
|
|
def _parse_parameter_declaration_body(self) -> c_ast.Node:
|
|
spec, _, spec_coord = self._parse_declaration_specifiers(allow_no_type=True)
|
|
|
|
if self._is_inferred_auto(spec):
|
|
self._parse_error("Cannot infer the type of a parameter", spec_coord)
|
|
|
|
if "typedef" in spec["storage"]:
|
|
self._parse_error("Invalid storage class for a parameter", spec_coord)
|
|
|
|
if not spec["type"]:
|
|
spec["type"] = [c_ast.IdentifierType(["int"], coord=spec_coord)]
|
|
|
|
if self._starts_declarator():
|
|
decl, is_named = self._parse_any_declarator(
|
|
allow_abstract=True, typeid_paren_as_abstract=True
|
|
)
|
|
if is_named:
|
|
return self._build_declarations(
|
|
spec=spec,
|
|
decls=[{"decl": decl}],
|
|
)[0]
|
|
return self._build_parameter_declaration(spec, decl, spec_coord)
|
|
|
|
decl = self._parse_abstract_declarator_opt()
|
|
return self._build_parameter_declaration(spec, decl, spec_coord)
|
|
|
|
def _build_parameter_declaration(
|
|
self, spec: "_DeclSpec", decl: c_ast.Node | None, spec_coord: Coord | None
|
|
) -> c_ast.Node:
|
|
if (
|
|
len(spec["type"]) > 1
|
|
and isinstance(spec["type"][-1], c_ast.IdentifierType)
|
|
and len(spec["type"][-1].names) == 1
|
|
and self._is_type_in_scope(spec["type"][-1].names[0])
|
|
):
|
|
return self._build_declarations(spec=spec, decls=[{"decl": decl}])[0]
|
|
|
|
return self._build_type_name(spec, decl, spec_coord)
|
|
|
|
# BNF: identifier_list_opt : identifier_list | empty
|
|
def _parse_identifier_list_opt(self) -> c_ast.Node | None:
|
|
if self._peek_type() == "RPAREN":
|
|
return None
|
|
return self._parse_identifier_list()
|
|
|
|
# BNF: identifier_list : identifier (',' identifier)*
|
|
def _parse_identifier_list(self) -> c_ast.Node:
|
|
first = self._parse_identifier()
|
|
params = c_ast.ParamList([first], first.coord)
|
|
while self._accept("COMMA"):
|
|
params.params.append(self._parse_identifier())
|
|
return params
|
|
|
|
# ------------------------------------------------------------------
|
|
# Abstract declarators
|
|
# ------------------------------------------------------------------
|
|
# BNF: type_name : specifier_qualifier_list abstract_declarator_opt
|
|
def _parse_type_name(self) -> c_ast.Typename:
|
|
spec = self._parse_specifier_qualifier_list()
|
|
decl = self._parse_abstract_declarator_opt()
|
|
|
|
coord = None
|
|
if decl is not None:
|
|
coord = decl.coord
|
|
elif spec["type"]:
|
|
coord = spec["type"][0].coord
|
|
|
|
return self._build_type_name(spec, decl, coord)
|
|
|
|
# BNF: abstract_declarator_opt : pointer? direct_abstract_declarator?
|
|
def _parse_abstract_declarator_opt(self) -> c_ast.Node | None:
|
|
if self._peek_type() == "TIMES":
|
|
ptr = self._parse_pointer()
|
|
if self._starts_direct_abstract_declarator():
|
|
decl = self._parse_direct_abstract_declarator()
|
|
else:
|
|
decl = c_ast.TypeDecl(None, None, None, None)
|
|
assert ptr is not None
|
|
return self._type_modify_decl(decl, ptr)
|
|
|
|
if self._starts_direct_abstract_declarator():
|
|
return self._parse_direct_abstract_declarator()
|
|
|
|
return None
|
|
|
|
# BNF: direct_abstract_declarator : '(' parameter_type_list_opt ')'
|
|
# | '(' abstract_declarator ')'
|
|
# | '[' ... ']'
|
|
def _parse_direct_abstract_declarator(self) -> c_ast.Node:
|
|
lparen_tok = self._accept("LPAREN")
|
|
if lparen_tok:
|
|
if self._starts_declaration() or self._peek_type() in {
|
|
"RPAREN",
|
|
"ELLIPSIS",
|
|
}:
|
|
params = self._parse_parameter_type_list_opt()
|
|
self._expect("RPAREN")
|
|
decl = c_ast.FuncDecl(
|
|
args=params,
|
|
type=c_ast.TypeDecl(None, None, None, None),
|
|
coord=self._tok_coord(lparen_tok),
|
|
attributes=self._parse_attribute_specifier_sequence(),
|
|
)
|
|
else:
|
|
decl = self._parse_abstract_declarator_opt()
|
|
self._expect("RPAREN")
|
|
assert decl is not None
|
|
elif self._peek_type() == "LBRACKET":
|
|
decl = self._parse_abstract_array_base()
|
|
else:
|
|
self._parse_error("Invalid abstract declarator", self.clex.filename)
|
|
|
|
return self._parse_decl_suffixes(decl)
|
|
|
|
# BNF: parameter_type_list_opt : parameter_type_list | empty
|
|
def _parse_parameter_type_list_opt(self) -> c_ast.ParamList | None:
|
|
if self._peek_type() == "RPAREN":
|
|
return None
|
|
return self._parse_parameter_type_list()
|
|
|
|
# BNF: abstract_array_base : '[' array_specifiers? assignment_expression? ']'
|
|
def _parse_abstract_array_base(self) -> c_ast.Node:
|
|
return self._parse_array_decl_common(
|
|
base_type=c_ast.TypeDecl(None, None, None, None), coord=None
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Statements
|
|
# ------------------------------------------------------------------
|
|
# BNF: statement : labeled_statement | compound_statement
|
|
# | selection_statement | iteration_statement
|
|
# | jump_statement | expression_statement
|
|
# | pppragma_directive
|
|
def _parse_statement(self) -> c_ast.Node:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
if self._accept("SEMI"):
|
|
return c_ast.AttributeDeclaration(attributes, attributes[0].coord)
|
|
return self._attribute_statement(self._parse_statement(), attributes)
|
|
tok_type = self._peek_type()
|
|
match tok_type:
|
|
case "CASE" | "DEFAULT":
|
|
return self._parse_labeled_statement()
|
|
case "ID" if self._peek_type(2) == "COLON":
|
|
return self._parse_labeled_statement()
|
|
case "LBRACE":
|
|
return self._parse_compound_statement()
|
|
case "IF" | "SWITCH":
|
|
return self._parse_selection_statement()
|
|
case "WHILE" | "DO" | "FOR":
|
|
return self._parse_iteration_statement()
|
|
case "GOTO" | "BREAK" | "CONTINUE" | "RETURN":
|
|
return self._parse_jump_statement()
|
|
case "PPPRAGMA" | "_PRAGMA":
|
|
return self._parse_pppragma_directive()
|
|
case _:
|
|
return self._parse_expression_statement()
|
|
|
|
# BNF: pragmacomp_or_statement : pppragma_directive* statement
|
|
def _parse_pragmacomp_or_statement(self) -> c_ast.Node:
|
|
if self._peek_type() in {"PPPRAGMA", "_PRAGMA"}:
|
|
pragmas = self._parse_pppragma_directive_list()
|
|
stmt = self._parse_statement()
|
|
return c_ast.Compound(block_items=pragmas + [stmt], coord=pragmas[0].coord)
|
|
return self._parse_statement()
|
|
|
|
# BNF: block_item : declaration | statement
|
|
def _parse_block_item(self) -> c_ast.Node | list[c_ast.Node]:
|
|
if self._peek_type() == "ATTRIBUTE_BEGIN":
|
|
attributes = self._parse_attribute_specifier_sequence()
|
|
if self._accept("SEMI"):
|
|
return c_ast.AttributeDeclaration(attributes, attributes[0].coord)
|
|
if self._starts_declaration():
|
|
return self._attribute_declarations(
|
|
self._parse_declaration(), attributes
|
|
)
|
|
return self._attribute_statement(self._parse_statement(), attributes)
|
|
if self._peek_type() == "_STATIC_ASSERT":
|
|
return self._parse_static_assert()
|
|
if self._starts_declaration():
|
|
return self._parse_declaration()
|
|
return self._parse_statement()
|
|
|
|
# BNF: block_item_list : block_item+
|
|
def _parse_block_item_list(self) -> list[c_ast.Node]:
|
|
items: list[c_ast.Node] = []
|
|
while self._peek_type() not in {"RBRACE", None}:
|
|
item = self._parse_block_item()
|
|
if isinstance(item, c_ast.Node):
|
|
items.append(item)
|
|
elif item != [None]:
|
|
items.extend(item)
|
|
return items
|
|
|
|
# BNF: compound_statement : '{' block_item_list? '}'
|
|
def _parse_compound_statement(self, *, new_scope: bool = True) -> c_ast.Node:
|
|
lbrace_tok = self._expect("LBRACE")
|
|
if new_scope:
|
|
self._push_scope()
|
|
block_items = None
|
|
if not self._accept("RBRACE"):
|
|
block_items = self._parse_block_item_list()
|
|
self._expect("RBRACE")
|
|
if new_scope:
|
|
self._pop_scope()
|
|
return c_ast.Compound(
|
|
block_items=block_items, coord=self._tok_coord(lbrace_tok)
|
|
)
|
|
|
|
# BNF: labeled_statement : ID ':' statement
|
|
# | CASE constant_expression ':' statement
|
|
# | DEFAULT ':' statement
|
|
def _parse_labeled_statement(self) -> c_ast.Node:
|
|
tok_type = self._peek_type()
|
|
match tok_type:
|
|
case "ID":
|
|
name_tok = self._advance()
|
|
self._expect("COLON")
|
|
if self._starts_statement():
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
else:
|
|
stmt = c_ast.EmptyStatement(self._tok_coord(name_tok))
|
|
return c_ast.Label(name_tok.value, stmt, self._tok_coord(name_tok))
|
|
case "CASE":
|
|
case_tok = self._advance()
|
|
expr = self._parse_constant_expression()
|
|
self._expect("COLON")
|
|
if self._starts_statement():
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
else:
|
|
stmt = c_ast.EmptyStatement(self._tok_coord(case_tok))
|
|
return c_ast.Case(expr, [stmt], self._tok_coord(case_tok))
|
|
case "DEFAULT":
|
|
def_tok = self._advance()
|
|
self._expect("COLON")
|
|
if self._starts_statement():
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
else:
|
|
stmt = c_ast.EmptyStatement(self._tok_coord(def_tok))
|
|
return c_ast.Default([stmt], self._tok_coord(def_tok))
|
|
case _:
|
|
self._parse_error("Invalid labeled statement", self.clex.filename)
|
|
|
|
# BNF: selection_statement : IF '(' expression ')' statement (ELSE statement)?
|
|
# | SWITCH '(' expression ')' statement
|
|
def _parse_selection_statement(self) -> c_ast.Node:
|
|
tok = self._advance()
|
|
match tok.type:
|
|
case "IF":
|
|
self._expect("LPAREN")
|
|
cond = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
then_stmt = self._parse_pragmacomp_or_statement()
|
|
if self._accept("ELSE"):
|
|
else_stmt = self._parse_pragmacomp_or_statement()
|
|
return c_ast.If(cond, then_stmt, else_stmt, self._tok_coord(tok))
|
|
return c_ast.If(cond, then_stmt, None, self._tok_coord(tok))
|
|
case "SWITCH":
|
|
self._expect("LPAREN")
|
|
expr = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
return fix_switch_cases(c_ast.Switch(expr, stmt, self._tok_coord(tok)))
|
|
case _:
|
|
self._parse_error("Invalid selection statement", self._tok_coord(tok))
|
|
|
|
# BNF: iteration_statement : WHILE '(' expression ')' statement
|
|
# | DO statement WHILE '(' expression ')' ';'
|
|
# | FOR '(' (declaration | expression_opt) ';'
|
|
# expression_opt ';' expression_opt ')' statement
|
|
def _parse_iteration_statement(self) -> c_ast.Node:
|
|
tok = self._advance()
|
|
match tok.type:
|
|
case "WHILE":
|
|
self._expect("LPAREN")
|
|
cond = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
return c_ast.While(cond, stmt, self._tok_coord(tok))
|
|
case "DO":
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
self._expect("WHILE")
|
|
self._expect("LPAREN")
|
|
cond = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
self._expect("SEMI")
|
|
return c_ast.DoWhile(cond, stmt, self._tok_coord(tok))
|
|
case "FOR":
|
|
self._expect("LPAREN")
|
|
if self._peek_type() == "_STATIC_ASSERT" or self._starts_declaration():
|
|
# Names declared here are scoped to the loop (C11 6.8.5p5)
|
|
self._push_scope()
|
|
decls = self._parse_declaration()
|
|
init = c_ast.DeclList(decls, self._tok_coord(tok))
|
|
cond = self._parse_expression_opt()
|
|
self._expect("SEMI")
|
|
next_expr = self._parse_expression_opt()
|
|
self._expect("RPAREN")
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
self._pop_scope()
|
|
return c_ast.For(init, cond, next_expr, stmt, self._tok_coord(tok))
|
|
|
|
init = self._parse_expression_opt()
|
|
self._expect("SEMI")
|
|
cond = self._parse_expression_opt()
|
|
self._expect("SEMI")
|
|
next_expr = self._parse_expression_opt()
|
|
self._expect("RPAREN")
|
|
stmt = self._parse_pragmacomp_or_statement()
|
|
return c_ast.For(init, cond, next_expr, stmt, self._tok_coord(tok))
|
|
case _:
|
|
self._parse_error("Invalid iteration statement", self._tok_coord(tok))
|
|
|
|
# BNF: jump_statement : GOTO ID ';' | BREAK ';' | CONTINUE ';'
|
|
# | RETURN expression? ';'
|
|
def _parse_jump_statement(self) -> c_ast.Node:
|
|
tok = self._advance()
|
|
match tok.type:
|
|
case "GOTO":
|
|
name_tok = self._expect("ID")
|
|
self._expect("SEMI")
|
|
return c_ast.Goto(name_tok.value, self._tok_coord(tok))
|
|
case "BREAK":
|
|
self._expect("SEMI")
|
|
return c_ast.Break(self._tok_coord(tok))
|
|
case "CONTINUE":
|
|
self._expect("SEMI")
|
|
return c_ast.Continue(self._tok_coord(tok))
|
|
case "RETURN":
|
|
if self._accept("SEMI"):
|
|
return c_ast.Return(None, self._tok_coord(tok))
|
|
expr = self._parse_expression()
|
|
self._expect("SEMI")
|
|
return c_ast.Return(expr, self._tok_coord(tok))
|
|
case _:
|
|
self._parse_error("Invalid jump statement", self._tok_coord(tok))
|
|
|
|
# BNF: expression_statement : expression_opt ';'
|
|
def _parse_expression_statement(self) -> c_ast.Node:
|
|
expr = self._parse_expression_opt()
|
|
semi_tok = self._expect("SEMI")
|
|
if expr is None:
|
|
return c_ast.EmptyStatement(self._tok_coord(semi_tok))
|
|
return expr
|
|
|
|
# ------------------------------------------------------------------
|
|
# Expressions
|
|
# ------------------------------------------------------------------
|
|
# BNF: expression_opt : expression | empty
|
|
def _parse_expression_opt(self) -> c_ast.Node | None:
|
|
if self._starts_expression():
|
|
return self._parse_expression()
|
|
return None
|
|
|
|
# BNF: expression : assignment_expression (',' assignment_expression)*
|
|
def _parse_expression(self) -> c_ast.Node:
|
|
expr = self._parse_assignment_expression()
|
|
if not self._accept("COMMA"):
|
|
return expr
|
|
exprs = [expr, self._parse_assignment_expression()]
|
|
while self._accept("COMMA"):
|
|
exprs.append(self._parse_assignment_expression())
|
|
return c_ast.ExprList(exprs, expr.coord)
|
|
|
|
# BNF: assignment_expression : conditional_expression
|
|
# | unary_expression assignment_op assignment_expression
|
|
def _parse_assignment_expression(self) -> c_ast.Node:
|
|
expr = self._parse_conditional_expression()
|
|
if self._is_assignment_op():
|
|
op = self._advance().value
|
|
rhs = self._parse_assignment_expression()
|
|
return c_ast.Assignment(op, expr, rhs, expr.coord)
|
|
return expr
|
|
|
|
# BNF: conditional_expression : binary_expression
|
|
# | binary_expression '?' expression ':' conditional_expression
|
|
def _parse_conditional_expression(self) -> c_ast.Node:
|
|
expr = self._parse_binary_expression()
|
|
if self._accept("CONDOP"):
|
|
iftrue = self._parse_expression()
|
|
self._expect("COLON")
|
|
iffalse = self._parse_conditional_expression()
|
|
return c_ast.TernaryOp(expr, iftrue, iffalse, expr.coord)
|
|
return expr
|
|
|
|
# BNF: binary_expression : cast_expression (binary_op cast_expression)*
|
|
def _parse_binary_expression(
|
|
self, min_prec: int = 0, lhs: c_ast.Node | None = None
|
|
) -> c_ast.Node:
|
|
if lhs is None:
|
|
lhs = self._parse_cast_expression()
|
|
|
|
while True:
|
|
tok = self._peek()
|
|
if tok is None or tok.type not in _BINARY_PRECEDENCE:
|
|
break
|
|
prec = _BINARY_PRECEDENCE[tok.type]
|
|
if prec < min_prec:
|
|
break
|
|
|
|
op = tok.value
|
|
self._advance()
|
|
rhs = self._parse_cast_expression()
|
|
|
|
while True:
|
|
next_tok = self._peek()
|
|
if next_tok is None or next_tok.type not in _BINARY_PRECEDENCE:
|
|
break
|
|
next_prec = _BINARY_PRECEDENCE[next_tok.type]
|
|
if next_prec > prec:
|
|
rhs = self._parse_binary_expression(next_prec, rhs)
|
|
else:
|
|
break
|
|
|
|
lhs = c_ast.BinaryOp(op, lhs, rhs, lhs.coord)
|
|
|
|
return lhs
|
|
|
|
# BNF: cast_expression : '(' type_name ')' cast_expression
|
|
# | unary_expression
|
|
def _parse_cast_expression(self) -> c_ast.Node:
|
|
result = self._try_parse_paren_type_name()
|
|
if result is not None:
|
|
typ, mark, lparen_tok, _ = result
|
|
if self._peek_type() == "LBRACE":
|
|
# (type){...} is a compound literal, not a cast. Examples:
|
|
# (int){1} -> compound literal, handled in postfix
|
|
# (int) x -> cast, handled below
|
|
self._reset(mark)
|
|
else:
|
|
expr = self._parse_cast_expression()
|
|
return c_ast.Cast(typ, expr, self._tok_coord(lparen_tok))
|
|
return self._parse_unary_expression()
|
|
|
|
# BNF: unary_expression : postfix_expression
|
|
# | '++' unary_expression
|
|
# | '--' unary_expression
|
|
# | unary_op cast_expression
|
|
# | 'sizeof' unary_expression
|
|
# | 'sizeof' '(' type_name ')'
|
|
# | '_Alignof' '(' type_name ')'
|
|
def _parse_unary_expression(self) -> c_ast.Node:
|
|
tok_type = self._peek_type()
|
|
if tok_type in {"PLUSPLUS", "MINUSMINUS"}:
|
|
tok = self._advance()
|
|
expr = self._parse_unary_expression()
|
|
return c_ast.UnaryOp(tok.value, expr, expr.coord)
|
|
|
|
if tok_type in {"AND", "TIMES", "PLUS", "MINUS", "NOT", "LNOT"}:
|
|
tok = self._advance()
|
|
expr = self._parse_cast_expression()
|
|
return c_ast.UnaryOp(tok.value, expr, expr.coord)
|
|
|
|
if tok_type == "SIZEOF":
|
|
tok = self._advance()
|
|
result = self._try_parse_paren_type_name()
|
|
if result is not None:
|
|
typ, mark, _, _ = result
|
|
if self._peek_type() != "LBRACE":
|
|
return c_ast.UnaryOp(tok.value, typ, self._tok_coord(tok))
|
|
# sizeof (type){...} has an expression operand, including
|
|
# any postfix operators following the compound literal.
|
|
self._reset(mark)
|
|
expr = self._parse_unary_expression()
|
|
return c_ast.UnaryOp(tok.value, expr, self._tok_coord(tok))
|
|
|
|
if tok_type == "_ALIGNOF":
|
|
tok = self._advance()
|
|
self._expect("LPAREN")
|
|
typ = self._parse_type_name()
|
|
self._expect("RPAREN")
|
|
return c_ast.UnaryOp(tok.value, typ, self._tok_coord(tok))
|
|
|
|
return self._parse_postfix_expression()
|
|
|
|
# BNF: postfix_expression : primary_expression postfix_suffix*
|
|
# | compound_literal postfix_suffix*
|
|
# compound_literal : '(' storage_class_specifiers? type_name ')'
|
|
# braced_initializer
|
|
def _parse_postfix_expression(self) -> c_ast.Node:
|
|
expr = None
|
|
result = self._try_parse_paren_type_name()
|
|
if result is not None:
|
|
typ, mark, lparen_tok, storage = result
|
|
# Disambiguate between casts and compound literals:
|
|
# (int) x -> cast
|
|
# (int) {1} -> compound literal
|
|
if self._peek_type() == "LBRACE":
|
|
init = self._parse_initializer()
|
|
expr = c_ast.CompoundLiteral(
|
|
typ, init, storage, self._tok_coord(lparen_tok)
|
|
)
|
|
else:
|
|
self._reset(mark)
|
|
|
|
if expr is None:
|
|
expr = self._parse_primary_expression()
|
|
while True:
|
|
tok_type = self._peek_type()
|
|
if tok_type == "LBRACKET":
|
|
self._advance()
|
|
sub = self._parse_expression()
|
|
self._expect("RBRACKET")
|
|
expr = c_ast.ArrayRef(expr, sub, expr.coord)
|
|
continue
|
|
if tok_type == "LPAREN":
|
|
self._advance()
|
|
if self._peek_type() == "RPAREN":
|
|
self._advance()
|
|
args = None
|
|
else:
|
|
args = self._parse_argument_expression_list()
|
|
self._expect("RPAREN")
|
|
expr = c_ast.FuncCall(expr, args, expr.coord)
|
|
continue
|
|
if tok_type in {"PERIOD", "ARROW"}:
|
|
op_tok = self._advance()
|
|
name_tok = self._advance()
|
|
if name_tok.type not in {"ID", "TYPEID"}:
|
|
self._parse_error(
|
|
"Invalid struct reference", self._tok_coord(name_tok)
|
|
)
|
|
field = c_ast.ID(name_tok.value, self._tok_coord(name_tok))
|
|
expr = c_ast.StructRef(expr, op_tok.value, field, expr.coord)
|
|
continue
|
|
if tok_type in {"PLUSPLUS", "MINUSMINUS"}:
|
|
tok = self._advance()
|
|
expr = c_ast.UnaryOp("p" + tok.value, expr, expr.coord)
|
|
continue
|
|
break
|
|
return expr
|
|
|
|
# BNF: primary_expression : ID | constant | string_literal
|
|
# | '(' expression ')' | generic_selection | offsetof
|
|
# | '(' compound_statement ')'
|
|
# | '__builtin_va_arg' '('
|
|
# assignment_expression ',' type_name ')'
|
|
def _parse_primary_expression(self) -> c_ast.Node:
|
|
tok_type = self._peek_type()
|
|
if tok_type == "ID":
|
|
return self._parse_identifier()
|
|
if (
|
|
tok_type in _INT_CONST
|
|
or tok_type in _FLOAT_CONST
|
|
or tok_type in _CHAR_CONST
|
|
or tok_type in _PREDEFINED_CONST
|
|
):
|
|
return self._parse_constant()
|
|
if tok_type in _STRING_LITERAL:
|
|
return self._parse_unified_string_literal()
|
|
if tok_type == "LPAREN":
|
|
self._advance()
|
|
if self._peek_type() == "LBRACE":
|
|
# GNU statement expressions participate in all expression
|
|
# operators, including postfix operators.
|
|
expr = self._parse_compound_statement()
|
|
else:
|
|
expr = self._parse_expression()
|
|
self._expect("RPAREN")
|
|
return expr
|
|
if tok_type == "_GENERIC":
|
|
return self._parse_generic_selection()
|
|
if tok_type == "__BUILTIN_VA_ARG":
|
|
tok = self._advance()
|
|
self._expect("LPAREN")
|
|
expr = self._parse_assignment_expression()
|
|
self._expect("COMMA")
|
|
typ = self._parse_type_name()
|
|
self._expect("RPAREN")
|
|
coord = self._tok_coord(tok)
|
|
return c_ast.FuncCall(
|
|
c_ast.ID(tok.value, coord),
|
|
c_ast.ExprList([expr, typ], coord),
|
|
coord,
|
|
)
|
|
if tok_type == "OFFSETOF":
|
|
off_tok = self._advance()
|
|
self._expect("LPAREN")
|
|
typ = self._parse_type_name()
|
|
self._expect("COMMA")
|
|
designator = self._parse_offsetof_member_designator()
|
|
self._expect("RPAREN")
|
|
coord = self._tok_coord(off_tok)
|
|
return c_ast.FuncCall(
|
|
c_ast.ID(off_tok.value, coord),
|
|
c_ast.ExprList([typ, designator], coord),
|
|
coord,
|
|
)
|
|
|
|
self._parse_error("Invalid expression", self.clex.filename)
|
|
|
|
# BNF: generic_selection : _GENERIC '(' assignment_expression ','
|
|
# generic_association (',' generic_association)* ')'
|
|
def _parse_generic_selection(self) -> c_ast.GenericSelection:
|
|
tok = self._expect("_GENERIC")
|
|
self._expect("LPAREN")
|
|
expr = self._parse_assignment_expression()
|
|
self._expect("COMMA")
|
|
associations = [self._parse_generic_association()]
|
|
while self._accept("COMMA"):
|
|
associations.append(self._parse_generic_association())
|
|
self._expect("RPAREN")
|
|
return c_ast.GenericSelection(expr, associations, self._tok_coord(tok))
|
|
|
|
# BNF: generic_association : type_name ':' assignment_expression
|
|
# | DEFAULT ':' assignment_expression
|
|
def _parse_generic_association(self) -> c_ast.GenericAssociation:
|
|
tok = self._peek()
|
|
if tok is None:
|
|
self._parse_error("At end of input", self.clex.filename)
|
|
typ = None if self._accept("DEFAULT") else self._parse_type_name()
|
|
self._expect("COLON")
|
|
expr = self._parse_assignment_expression()
|
|
return c_ast.GenericAssociation(typ, expr, self._tok_coord(tok))
|
|
|
|
# BNF: offsetof_member_designator : identifier_or_typeid
|
|
# ('.' identifier_or_typeid | '[' expression ']')*
|
|
def _parse_offsetof_member_designator(self) -> c_ast.Node:
|
|
node = self._parse_identifier_or_typeid()
|
|
while True:
|
|
if self._accept("PERIOD"):
|
|
field = self._parse_identifier_or_typeid()
|
|
node = c_ast.StructRef(node, ".", field, node.coord)
|
|
continue
|
|
if self._accept("LBRACKET"):
|
|
expr = self._parse_expression()
|
|
self._expect("RBRACKET")
|
|
node = c_ast.ArrayRef(node, expr, node.coord)
|
|
continue
|
|
break
|
|
return node
|
|
|
|
# BNF: argument_expression_list : assignment_expression (',' assignment_expression)*
|
|
def _parse_argument_expression_list(self) -> c_ast.Node:
|
|
expr = self._parse_assignment_expression()
|
|
exprs = [expr]
|
|
while self._accept("COMMA"):
|
|
exprs.append(self._parse_assignment_expression())
|
|
return c_ast.ExprList(exprs, expr.coord)
|
|
|
|
# BNF: constant_expression : conditional_expression
|
|
def _parse_constant_expression(self) -> c_ast.Node:
|
|
return self._parse_conditional_expression()
|
|
|
|
# ------------------------------------------------------------------
|
|
# Terminals
|
|
# ------------------------------------------------------------------
|
|
# BNF: identifier : ID
|
|
def _parse_identifier(self) -> c_ast.Node:
|
|
tok = self._expect("ID")
|
|
return c_ast.ID(tok.value, self._tok_coord(tok))
|
|
|
|
# BNF: identifier_or_typeid : ID | TYPEID
|
|
def _parse_identifier_or_typeid(self) -> c_ast.ID:
|
|
tok = self._advance()
|
|
if tok.type not in {"ID", "TYPEID"}:
|
|
self._parse_error("Expected identifier", self._tok_coord(tok))
|
|
return c_ast.ID(tok.value, self._tok_coord(tok))
|
|
|
|
# BNF: constant : INT_CONST | FLOAT_CONST | CHAR_CONST | predefined_constant
|
|
def _parse_constant(self) -> c_ast.Node:
|
|
tok = self._advance()
|
|
if tok.type in _PREDEFINED_CONST:
|
|
return c_ast.Constant(
|
|
_PREDEFINED_CONST[tok.type], tok.value, self._tok_coord(tok)
|
|
)
|
|
|
|
if tok.type in _INT_CONST:
|
|
# Integer suffixes are at most three characters (e.g. ull, wbu).
|
|
suffix = tok.value[-3:].lower()
|
|
if suffix.endswith(("wb", "wbu")):
|
|
if self._dialect != "c23":
|
|
self._parse_error(
|
|
"Bit-precise integer constants require C23",
|
|
self._tok_coord(tok),
|
|
)
|
|
# Classify by suffix, as with other integer constants; retain
|
|
# the spelling without computing the value or its bit width.
|
|
t = "unsigned _BitInt" if "u" in suffix else "_BitInt"
|
|
return c_ast.Constant(t, tok.value, self._tok_coord(tok))
|
|
|
|
u_count = 0
|
|
l_count = 0
|
|
for ch in tok.value[-3:]:
|
|
if ch in ("l", "L"):
|
|
l_count += 1
|
|
elif ch in ("u", "U"):
|
|
u_count += 1
|
|
if u_count > 1:
|
|
raise ValueError("Constant cannot have more than one u/U suffix.")
|
|
if l_count > 2:
|
|
raise ValueError("Constant cannot have more than two l/L suffix.")
|
|
prefix = "unsigned " * u_count + "long " * l_count
|
|
return c_ast.Constant(prefix + "int", tok.value, self._tok_coord(tok))
|
|
|
|
if tok.type in _FLOAT_CONST:
|
|
if tok.value[-2:] in {"df", "DF"}:
|
|
t = "_Decimal32"
|
|
elif tok.value[-2:] in {"dd", "DD"}:
|
|
t = "_Decimal64"
|
|
elif tok.value[-2:] in {"dl", "DL"}:
|
|
t = "_Decimal128"
|
|
elif tok.value[-1] in ("f", "F"):
|
|
t = "float"
|
|
elif tok.value[-1] in ("l", "L"):
|
|
t = "long double"
|
|
else:
|
|
t = "double"
|
|
if (
|
|
t in {"_Decimal32", "_Decimal64", "_Decimal128"}
|
|
and self._dialect != "c23"
|
|
):
|
|
self._parse_error(
|
|
"Decimal floating constants require C23", self._tok_coord(tok)
|
|
)
|
|
return c_ast.Constant(t, tok.value, self._tok_coord(tok))
|
|
|
|
if tok.type in _CHAR_CONST:
|
|
return c_ast.Constant("char", tok.value, self._tok_coord(tok))
|
|
|
|
self._parse_error("Invalid constant", self._tok_coord(tok))
|
|
|
|
# BNF: unified_string_literal : (STRING_LITERAL | WSTRING_LITERAL
|
|
# | U8STRING_LITERAL | U16STRING_LITERAL
|
|
# | U32STRING_LITERAL)+
|
|
def _parse_unified_string_literal(self) -> c_ast.Constant:
|
|
# Represent the whole run of adjacent literals with one Constant.
|
|
# C interprets escapes before concatenating the character sequences,
|
|
# whereas Constant.value holds source spelling. Simply removing the
|
|
# quotes between "\1" "23" would produce "\123", changing three
|
|
# characters into one (issue #392).
|
|
tok = self._advance()
|
|
if tok.type not in _STRING_LITERAL:
|
|
self._parse_error("Invalid string literal", self._tok_coord(tok))
|
|
# Split off the optional encoding prefix and remove the closing quote.
|
|
# Each part holds unquoted source text that can safely share one literal.
|
|
prefix, contents = tok.value.split('"', 1)
|
|
parts = [contents[:-1]]
|
|
while self._peek_type() in _STRING_LITERAL:
|
|
tok2 = self._advance()
|
|
next_prefix, next_contents = tok2.value.split('"', 1)
|
|
# The first nonempty prefix applies to the whole run, even if it
|
|
# occurs after ordinary literals. Conflicting prefixes retain the
|
|
# parser's existing permissive behavior: the first one wins.
|
|
prefix = prefix or next_prefix
|
|
next_contents = next_contents[:-1]
|
|
# Extend the current part when safe; otherwise start a new one so
|
|
# a quote boundary survives. Empty literals can merge, but do not
|
|
# end a trailing escape: "\1" "" "23" still needs a boundary.
|
|
if self._string_literal_needs_separator(parts[-1], next_contents):
|
|
parts.append(next_contents)
|
|
else:
|
|
parts[-1] += next_contents
|
|
# Re-quote each part, keeping only the necessary literal boundaries.
|
|
# The prefix on the first literal applies to the adjacent sequence.
|
|
# The generator can emit this value directly without decoding escapes.
|
|
value = prefix + '"' + '" "'.join(parts) + '"'
|
|
return c_ast.Constant("string", value, self._tok_coord(tok))
|
|
|
|
@staticmethod
|
|
def _string_literal_needs_separator(left: str, right: str) -> bool:
|
|
"""Whether joining two literal bodies would change their C spelling."""
|
|
if not left or not right:
|
|
return False
|
|
|
|
# A generated trigraph would be replaced before C string processing;
|
|
# for example, merging "?" "?=" would create the trigraph for '#'.
|
|
boundary = left[-2:] + right[:2]
|
|
if any("??" + ch in boundary for ch in "=/'()!<>-"):
|
|
return True
|
|
|
|
# Only an escape reaching the end of left can absorb characters from
|
|
# right. The last backslash is the only possible start of that escape.
|
|
escape_start = left.rfind("\\")
|
|
if escape_start == -1:
|
|
return False
|
|
# An even run of backslashes represents escaped backslashes, not the
|
|
# start of the numeric escape that the following digits might suggest.
|
|
run_start = escape_start
|
|
while run_start > 0 and left[run_start - 1] == "\\":
|
|
run_start -= 1
|
|
if (escape_start - run_start + 1) % 2 == 0:
|
|
return False
|
|
|
|
escape = left[escape_start + 1 :]
|
|
if escape.startswith("x"):
|
|
# Hex escapes have no digit limit: "\x1" "a" must stay separated.
|
|
hex_digits = "0123456789abcdefABCDEF"
|
|
return right[0] in hex_digits and all(ch in hex_digits for ch in escape[1:])
|
|
# Octal escapes consume at most three digits: "\12" "3" needs a
|
|
# boundary, but "\123" "4" can become "\1234". Universal character names
|
|
# have fixed widths and cannot absorb characters from the next literal.
|
|
return (
|
|
1 <= len(escape) < 3
|
|
and all(ch in "01234567" for ch in escape)
|
|
and right[0] in "01234567"
|
|
)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Initializers
|
|
# ------------------------------------------------------------------
|
|
# BNF: initializer : assignment_expression
|
|
# | '{' initializer_list ','? '}'
|
|
# | '{' '}'
|
|
def _parse_initializer(self) -> c_ast.Node:
|
|
lbrace_tok = self._accept("LBRACE")
|
|
if lbrace_tok:
|
|
if self._accept("RBRACE"):
|
|
return c_ast.InitList([], self._tok_coord(lbrace_tok))
|
|
init_list = self._parse_initializer_list()
|
|
self._accept("COMMA")
|
|
self._expect("RBRACE")
|
|
return init_list
|
|
|
|
return self._parse_assignment_expression()
|
|
|
|
# BNF: initializer_list : initializer_item (',' initializer_item)* ','?
|
|
def _parse_initializer_list(self) -> c_ast.Node:
|
|
items = [self._parse_initializer_item()]
|
|
while self._accept("COMMA"):
|
|
if self._peek_type() == "RBRACE":
|
|
break
|
|
items.append(self._parse_initializer_item())
|
|
return c_ast.InitList(items, items[0].coord)
|
|
|
|
# BNF: initializer_item : designation? initializer
|
|
def _parse_initializer_item(self) -> c_ast.Node:
|
|
designation = None
|
|
if self._peek_type() in {"LBRACKET", "PERIOD"}:
|
|
designation = self._parse_designation()
|
|
init = self._parse_initializer()
|
|
if designation is not None:
|
|
return c_ast.NamedInitializer(designation, init)
|
|
return init
|
|
|
|
# BNF: designation : designator_list '='
|
|
def _parse_designation(self) -> list[c_ast.Node]:
|
|
designators = self._parse_designator_list()
|
|
self._expect("EQUALS")
|
|
return designators
|
|
|
|
# BNF: designator_list : designator+
|
|
def _parse_designator_list(self) -> list[c_ast.Node]:
|
|
designators = []
|
|
while self._peek_type() in {"LBRACKET", "PERIOD"}:
|
|
designators.append(self._parse_designator())
|
|
return designators
|
|
|
|
# BNF: designator : '[' constant_expression ']'
|
|
# | '.' identifier_or_typeid
|
|
def _parse_designator(self) -> c_ast.Node:
|
|
if tok := self._accept("LBRACKET"):
|
|
expr = self._parse_constant_expression()
|
|
self._expect("RBRACKET")
|
|
return c_ast.ArrayDesignator(expr, self._tok_coord(tok))
|
|
if self._accept("PERIOD"):
|
|
return self._parse_identifier_or_typeid()
|
|
self._parse_error("Invalid designator", self.clex.filename)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Preprocessor-like directives
|
|
# ------------------------------------------------------------------
|
|
# BNF: pp_directive : '#' ... (unsupported)
|
|
def _parse_pp_directive(self) -> NoReturn:
|
|
tok = self._expect("PPHASH")
|
|
self._parse_error("Directives not supported yet", self._tok_coord(tok))
|
|
|
|
# BNF: pppragma_directive : PPPRAGMA PPPRAGMASTR?
|
|
# | _PRAGMA '(' string_literal ')'
|
|
def _parse_pppragma_directive(self) -> c_ast.Node:
|
|
if self._peek_type() == "PPPRAGMA":
|
|
tok = self._advance()
|
|
if self._peek_type() == "PPPRAGMASTR":
|
|
str_tok = self._advance()
|
|
return c_ast.Pragma(str_tok.value, self._tok_coord(str_tok))
|
|
return c_ast.Pragma("", self._tok_coord(tok))
|
|
|
|
if self._peek_type() == "_PRAGMA":
|
|
tok = self._advance()
|
|
lparen = self._expect("LPAREN")
|
|
literal = self._parse_unified_string_literal()
|
|
self._expect("RPAREN")
|
|
return c_ast.Pragma(literal, self._tok_coord(lparen))
|
|
|
|
self._parse_error("Invalid pragma", self.clex.filename)
|
|
|
|
# BNF: pppragma_directive_list : pppragma_directive+
|
|
def _parse_pppragma_directive_list(self) -> list[c_ast.Node]:
|
|
pragmas = []
|
|
while self._peek_type() in {"PPPRAGMA", "_PRAGMA"}:
|
|
pragmas.append(self._parse_pppragma_directive())
|
|
return pragmas
|
|
|
|
# BNF: static_assert : _STATIC_ASSERT '(' constant_expression
|
|
# (',' unified_string_literal)? ')' ';'
|
|
# C11 requires a message; omission is supported as an extension.
|
|
def _parse_static_assert(self) -> c_ast.StaticAssert:
|
|
tok = self._expect("_STATIC_ASSERT")
|
|
self._expect("LPAREN")
|
|
cond = self._parse_constant_expression()
|
|
msg = None
|
|
if self._accept("COMMA"):
|
|
msg = self._parse_unified_string_literal()
|
|
self._expect("RPAREN")
|
|
self._expect("SEMI")
|
|
return c_ast.StaticAssert(cond, msg, self._tok_coord(tok))
|
|
|
|
|
|
_ASSIGNMENT_OPS = {
|
|
"EQUALS",
|
|
"XOREQUAL",
|
|
"TIMESEQUAL",
|
|
"DIVEQUAL",
|
|
"MODEQUAL",
|
|
"PLUSEQUAL",
|
|
"MINUSEQUAL",
|
|
"LSHIFTEQUAL",
|
|
"RSHIFTEQUAL",
|
|
"ANDEQUAL",
|
|
"OREQUAL",
|
|
}
|
|
|
|
# Precedence of operators (lower number = weather binding)
|
|
# If this changes, c_generator.CGenerator.precedence_map needs to change as
|
|
# well
|
|
_BINARY_PRECEDENCE = {
|
|
"LOR": 0,
|
|
"LAND": 1,
|
|
"OR": 2,
|
|
"XOR": 3,
|
|
"AND": 4,
|
|
"EQ": 5,
|
|
"NE": 5,
|
|
"GT": 6,
|
|
"GE": 6,
|
|
"LT": 6,
|
|
"LE": 6,
|
|
"RSHIFT": 7,
|
|
"LSHIFT": 7,
|
|
"PLUS": 8,
|
|
"MINUS": 8,
|
|
"TIMES": 9,
|
|
"DIVIDE": 9,
|
|
"MOD": 9,
|
|
}
|
|
|
|
_STORAGE_CLASS = {
|
|
"AUTO",
|
|
"CONSTEXPR",
|
|
"REGISTER",
|
|
"STATIC",
|
|
"EXTERN",
|
|
"TYPEDEF",
|
|
"_THREAD_LOCAL",
|
|
}
|
|
|
|
_FUNCTION_SPEC = {"INLINE", "_NORETURN"}
|
|
|
|
_TYPE_QUALIFIER = {"CONST", "RESTRICT", "VOLATILE", "_ATOMIC"}
|
|
|
|
_TYPEOF_SPEC = {"TYPEOF", "TYPEOF_UNQUAL"}
|
|
|
|
_TYPE_SPEC_SIMPLE = {
|
|
"VOID",
|
|
"_BOOL",
|
|
"CHAR",
|
|
"SHORT",
|
|
"INT",
|
|
"LONG",
|
|
"FLOAT",
|
|
"DOUBLE",
|
|
"_DECIMAL32",
|
|
"_DECIMAL64",
|
|
"_DECIMAL128",
|
|
"_COMPLEX",
|
|
"SIGNED",
|
|
"UNSIGNED",
|
|
"__INT128",
|
|
}
|
|
|
|
# Struct members without declarators carry a type specifier directly instead.
|
|
_DECLARATOR_NODES = (c_ast.TypeDecl, c_ast.PtrDecl, c_ast.ArrayDecl, c_ast.FuncDecl)
|
|
|
|
_DECL_START = (
|
|
_STORAGE_CLASS
|
|
| _FUNCTION_SPEC
|
|
| _TYPE_QUALIFIER
|
|
| _TYPE_SPEC_SIMPLE
|
|
| _TYPEOF_SPEC
|
|
| {
|
|
"TYPEID",
|
|
"STRUCT",
|
|
"UNION",
|
|
"ENUM",
|
|
"_ALIGNAS",
|
|
"_ATOMIC",
|
|
"_BITINT",
|
|
"ATTRIBUTE_BEGIN",
|
|
}
|
|
)
|
|
|
|
_EXPR_START = {
|
|
"ID",
|
|
"LPAREN",
|
|
"PLUSPLUS",
|
|
"MINUSMINUS",
|
|
"PLUS",
|
|
"MINUS",
|
|
"TIMES",
|
|
"AND",
|
|
"NOT",
|
|
"LNOT",
|
|
"SIZEOF",
|
|
"_ALIGNOF",
|
|
"_GENERIC",
|
|
"OFFSETOF",
|
|
"__BUILTIN_VA_ARG",
|
|
}
|
|
|
|
_INT_CONST = {
|
|
"INT_CONST_DEC",
|
|
"INT_CONST_OCT",
|
|
"INT_CONST_HEX",
|
|
"INT_CONST_BIN",
|
|
"INT_CONST_CHAR",
|
|
}
|
|
|
|
_FLOAT_CONST = {"FLOAT_CONST", "HEX_FLOAT_CONST"}
|
|
|
|
_PREDEFINED_CONST = {"BOOL_CONST": "bool", "NULLPTR": "nullptr_t"}
|
|
|
|
_CHAR_CONST = {
|
|
"CHAR_CONST",
|
|
"WCHAR_CONST",
|
|
"U8CHAR_CONST",
|
|
"U16CHAR_CONST",
|
|
"U32CHAR_CONST",
|
|
}
|
|
|
|
_STRING_LITERAL = {
|
|
"STRING_LITERAL",
|
|
"WSTRING_LITERAL",
|
|
"U8STRING_LITERAL",
|
|
"U16STRING_LITERAL",
|
|
"U32STRING_LITERAL",
|
|
}
|
|
|
|
_STARTS_EXPRESSION = (
|
|
_EXPR_START
|
|
| _INT_CONST
|
|
| _FLOAT_CONST
|
|
| _CHAR_CONST
|
|
| _STRING_LITERAL
|
|
| _PREDEFINED_CONST.keys()
|
|
)
|
|
|
|
_STARTS_STATEMENT = {
|
|
"LBRACE",
|
|
"IF",
|
|
"SWITCH",
|
|
"WHILE",
|
|
"DO",
|
|
"FOR",
|
|
"GOTO",
|
|
"BREAK",
|
|
"CONTINUE",
|
|
"RETURN",
|
|
"CASE",
|
|
"DEFAULT",
|
|
"PPPRAGMA",
|
|
"_PRAGMA",
|
|
"SEMI",
|
|
}
|
|
|
|
|
|
class _TokenStream:
|
|
"""Provides buffered access to tokens supplied by a reader."""
|
|
|
|
def __init__(self, read_token: Callable[[], Token | None]) -> None:
|
|
self._read_token = read_token
|
|
self._buffer: list[Token | None] = []
|
|
self._index = 0
|
|
|
|
def peek(self, k: int = 1) -> Token | None:
|
|
"""Peek at the k-th next token in the stream, without consuming it.
|
|
|
|
Examples:
|
|
k=1 returns the immediate next token.
|
|
k=2 returns the token after that.
|
|
"""
|
|
if k <= 0:
|
|
return None
|
|
pos = self._index + k - 1
|
|
if pos >= len(self._buffer):
|
|
self._fill(k)
|
|
return self._buffer[pos]
|
|
|
|
def next(self) -> Token | None:
|
|
"""Consume a single token and return it."""
|
|
if self._index >= len(self._buffer):
|
|
self._fill(1)
|
|
tok = self._buffer[self._index]
|
|
self._index += 1
|
|
return tok
|
|
|
|
# The 'mark' and 'reset' methods are useful for speculative parsing with
|
|
# backtracking; when the parser needs to examine a sequence of tokens
|
|
# and potentially decide to try a different path on the same sequence, it
|
|
# can call 'mark' to obtain the current token position, and if the first
|
|
# path fails restore the position with `reset(pos)`.
|
|
def mark(self) -> int:
|
|
return self._index
|
|
|
|
def reset(self, mark: int) -> None:
|
|
self._index = mark
|
|
|
|
def _fill(self, n: int) -> None:
|
|
while len(self._buffer) < self._index + n:
|
|
tok = self._read_token()
|
|
self._buffer.append(tok)
|
|
if tok is None:
|
|
break
|
|
|
|
|
|
# Declaration specifiers are represented by a dictionary with entries:
|
|
# - qual: a list of type qualifiers
|
|
# - storage: a list of storage class specifiers
|
|
# - type: a list of type specifiers
|
|
# - function: a list of function specifiers
|
|
# - alignment: a list of alignment specifiers
|
|
# - type_attributes: attribute groups following the specifier sequence
|
|
class _DeclSpec(TypedDict):
|
|
qual: list[str]
|
|
storage: list[str]
|
|
type: list[c_ast.Node]
|
|
function: list[str]
|
|
alignment: list[c_ast.Alignas]
|
|
type_attributes: list[c_ast.AttributeSpecifier]
|
|
|
|
|
|
_DeclSpecKind = Literal["qual", "storage", "type", "function", "alignment"]
|
|
|
|
|
|
class _DeclInfo(TypedDict):
|
|
# Declarator payloads used by declaration/initializer parsing:
|
|
# - decl: the declarator node (may be None for abstract/implicit cases)
|
|
# - init: initializer expression; the key may be omitted when absent
|
|
# - bitsize: bit-field width expression; the key may be omitted when absent
|
|
decl: c_ast.Node | None
|
|
init: NotRequired[c_ast.Node | None]
|
|
bitsize: NotRequired[c_ast.Node | None]
|