ircmaxell/php-c-parser
Parse C source files into an AST in PHP (PHP 8.4+), including preprocessing resolution. Use a Context to set #define values (ints, strings, identifiers) before parsing. Great for analysis tools, code inspection, and experimentation.
Installation: Add the package via Composer:
composer require ircmaxell/php-c-parser
Ensure your project uses PHP 8.4+ (check php -v and composer.json constraints).
First Parse: Parse a C file into an AST:
use PHPCParser\CParser;
$parser = new CParser();
$ast = $parser->parse(__DIR__ . '/path/to/your/file.c');
Verify the AST structure (e.g., print_r($ast) or var_dump($ast)).
Preprocessor Context (Optional):
For files with #define directives, create a Context:
use PHPCParser\Context;
$context = new Context();
$context->defineInt('DEBUG', 1); // Simulate `#define DEBUG 1`
$ast = $parser->parse('file.c', $context);
FunctionDefinition, VariableDeclaration). Refer to the package’s internal classes (e.g., PHPCParser\Node\*).#include, #ifdef, or complex macros to validate directive resolution.$ast = $parser->parse('example.c');
$functions = collect($ast->children())
->where(fn ($node) => $node instanceof PHPCParser\Node\FunctionDefinition)
->map(fn ($node) => $node->name->toString());
// Output: ["foo", "bar", ...]
Use this to generate PHP FFI bindings or documentation.
$contexts = [
(new Context())->defineInt('DEBUG', 1),
(new Context())->defineInt('DEBUG', 0),
];
$asts = collect($contexts)->map(fn ($ctx) => $parser->parse('file.c', $ctx));
class BufferAllocationVisitor implements PHPCParser\Visitor {
public function visitNode(PHPCParser\Node $node) {
if ($node instanceof PHPCParser\Node\CallExpression) {
$callee = $node->callee;
if ($callee->name->toString() === 'malloc') {
echo "Found malloc at line {$node->startLine}\n";
}
}
return $node; // Continue traversal
}
}
$visitor = new BufferAllocationVisitor();
$parser->parse('file.c')->accept($visitor);
$files = glob('src/*.c');
foreach ($files as $file) {
$ast = $parser->parse($file);
// Process AST in chunks or stream results
yield $ast;
}
$ast = $parser->parse('lib.c');
$functions = $ast->children()
->filter(fn ($node) => $node instanceof PHPCParser\Node\FunctionDefinition);
foreach ($functions as $func) {
$ffiType = match ($func->returnType->toString()) {
'int' => 'int',
'char*' => 'string',
default => 'mixed',
};
echo "FFI::cdef('{$ffiType} {$func->name}(...);');\n";
}
storage/framework/views or use Blade templates.// app/Console/Commands/ParseCHeaders.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use PHPCParser\CParser;
class ParseCHeaders extends Command {
protected $signature = 'c:parse {file}';
protected $description = 'Parse a C file and output its AST';
public function handle(CParser $parser) {
$ast = $parser->parse($this->argument('file'));
$this->info(json_encode($ast, JSON_PRETTY_PRINT));
}
}
Register in app/Console/Kernel.php:
protected $commands = [
\App\Console\Commands\ParseCHeaders::class,
];
// app/Providers/AppServiceProvider.php
use Illuminate\Support\ServiceProvider;
use PHPCParser\CParser;
class AppServiceProvider extends ServiceProvider {
public function register() {
$this->app->singleton(CParser::class, fn () => new CParser());
}
}
// tests/Feature/CParserTest.php
use PHPCParser\CParser;
use Tests\TestCase;
class CParserTest extends TestCase {
public function testParsesSimpleFunction() {
$parser = new CParser();
$ast = $parser->parse(__DIR__ . '/fixtures/simple.c');
$this->assertInstanceOf(
PHPCParser\Node\FunctionDefinition::class,
$ast->children()[0]
);
}
}
Preprocessor Directive Limits
clang -E for preprocessing if needed.Memory Intensive Parsing
linux/kernel.h) may exhaust memory.ini_set('memory_limit', '2G') temporarily.AST Node Instability
PHPCParser\Node\Type renames).composer.lock) and abstract node access behind interfaces:
interface ASTNode {
public function toString();
}
Undefined Macro Errors
#define directives cause parsing to fail silently or throw exceptions.Context with common macros or validate files pre-parsing:
try {
$ast = $parser->parse('file.c', $context);
} catch (\PHPCParser\Exception $e) {
$this->handleMissingMacro($e->getMessage());
}
Cross-Platform Path Handling
\ vs /) may break file resolution.$filePath = str_replace('\\', '/', realpath('path/to/file.c'));
$parser = new CParser();
$parser->setLogger(new \Monolog\Logger('c_parser', [
new \Monolog\Handler\StreamHandler(storage_path('logs/c_parser.log'))
]));
clang -cc1 -ast-dump file.c to validate the parser’s AST structure matches expectations.$this->dumpAst($ast);
private function dumpAst($node, string $indent = '') {
echo $indent . get_class($node) . "\n";
foreach ($node->children() as $child) {
$this->dumpAst($child, $indent . ' ');
}
}
PHPCParser\Visitor to add domain-specific logic (e.g., security checks):
class SecurityVisitor implements Visitor {
public function visitNode(Node $node) {
if ($node instanceof CallExpression && $node->callee->name === 'memcpy') {
$this->flagUnsafeCopy($node);
}
How can I help you explore Laravel packages today?