Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion lib/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,38 @@ private function locateGCCHeaderPaths() {
}
}

public function findHeaderFile(string $header, string $contextDir, string $contextFile, bool $next): ?string {
if ($header[0] === '/' || ($header[1] === ':' && $header[2] === '\\')) {
if (file_exists($header)) {
return $header;
}
} else {
if ($contextDir && !$next) {
$dir = $contextDir;
while (!empty($dir) && $dir !== '/') {
$file = "$dir/$header";
if (file_exists($file)) {
return $file;
}
$dir = dirname($dir);
}
}
foreach ($this->headerSearchPaths as $path) {
if ($next) {
if ($contextDir === $path) {
$next = false;
}
break;
}
$test = $path . '/' . $header;
if (file_exists($test)) {
return $test;
}
}
}
return null;
}

public function getDefines(): array {
return $this->definitions;
}
Expand Down Expand Up @@ -142,7 +174,7 @@ public function evaluate(?Token $expr): Token {
return new Token(Token::NUMBER, '0', 'computed');
}
return $expr;
}
}
list ($result, $expr) = $this->evaluateInternal($expr);
if ($expr !== null) {
throw new \LogicException('Syntax error: unknown trailing expr: ' . $expr->value);
Expand Down Expand Up @@ -187,6 +219,38 @@ public function evaluateInternal(?Token $expr, bool $single = false): array {
} else {
throw new \LogicException("Syntax Error for #defined expression, expecting ( or IDENTIFIER, found " . $expr->value);
}
} elseif ($expr->type === Token::IDENTIFIER && $expr->value === '__has_include') {
$expr = Token::skipWhitespace($expr->next);
if ($expr === null) {
throw new \LogicException("Syntax Error for __has_include() expression: not enough tokens");
}
if ($expr->type === Token::PUNCTUATOR && $expr->value === '(') {
$expr = Token::skipWhitespace($expr->next);
if ($expr->type === Token::LITERAL) {
$file = $expr->value;
} elseif ($expr->type === Token::PUNCTUATOR && $expr->value === '<') {
// handle <> include:
$file = '';
while (!empty($expr->next)) {
$expr = $expr->next;
if ($expr->type === Token::PUNCTUATOR && $expr->value === '>') {
break;
}
$file .= $expr->value;
}
} else {
throw new \LogicException("Syntax Error for __has_include() expression, expecting < or LITERAL, found " . $expr->value);
}
$expr = Token::skipWhitespace($expr->next);
if ($expr === null || $expr->type !== Token::PUNCTUATOR && $expr->value !== ')') {
throw new \LogicException("Syntax Error for __has_include() expression: ) not found");
}
$contextDir = dirname($expr->file);
$result = new Token(Token::NUMBER, $this->findHeaderFile($file, $contextDir, $expr->file, false) !== null ? '1' : '0', 'computed');
$expr = Token::skipWhitespace($expr->next);
} else {
throw new \LogicException("Syntax Error for #__has_include expression, expecting (, found " . $expr->value);
}
} elseif ($expr->type === Token::IDENTIFIER) {
$next = Token::skipWhitespace($expr->next);
if ($next !== null && $next->value === '(') {
Expand Down Expand Up @@ -221,6 +285,7 @@ public function evaluateInternal(?Token $expr, bool $single = false): array {
$result = new Token(Token::NUMBER, $expr->value, 'computed');
$expr = Token::skipWhitespace($expr->next);
} else {
var_dump($expr);
throw new \LogicException('Unknown operator ' . $expr->value);
}
if ($negate) {
Expand Down Expand Up @@ -362,6 +427,8 @@ private function normalize(Token $expr): int {
throw new \LogicException("Base mismatch for {$str}, found $chr for $idx");
}
$result = ($result * $base) + $chr;
} elseif ($str[$idx] === 'U') {
// unsigned number, let's not touch it
} elseif ($str[$idx] === 'L') {
// indicates number is a long, return as is
if ($idx + 1 !== $length) {
Expand Down Expand Up @@ -389,7 +456,28 @@ public function doCall(string $toCall, ?Token ...$args): Token {
$argIdx = 0;
if ($token->value === '(') {
$token = Token::skipWhitespace($token->next);
$isVariadic = false;
while ($token !== null && $token->value !== ')') {
if ($isVariadic) {
throw new \LogicException('Unexpected token found, expecting ) after ... found ' . $token->value);
}
if ($token->type === Token::PUNCTUATOR && $token->value === "...") {
$isVariadic = true;
if (isset($args[$argIdx])) {
$argMap["__VA_ARGS__"] = $variadic = $args[$argIdx++];
while (isset($args[$argIdx])) {
while ($variadic->next) {
$variadic = $variadic->next;
}
$variadic->next = $args[$argIdx++];
}
} else {
$argMap["__VA_ARGS__"] = new Token(Token::OTHER, '', 'computed');
}
$token = Token::skipWhitespace($token->next);
continue;
}

if ($token->type !== Token::IDENTIFIER) {
throw new \LogicException('Unexpected argument found, expecting IDENTIFIER found ' . $token->value);
} elseif (!array_key_exists($argIdx, $args)) {
Expand All @@ -413,6 +501,16 @@ public function doCall(string $toCall, ?Token ...$args): Token {
// Copy token stream
$first = $newToken = new Token(0, '', 'internal');
while ($token !== null) {
// handle , ##__VA_ARGS__
if ($token->type === Token::PUNCTUATOR && $token->value === ',') {
$nextToken = Token::skipWhitespace($token);
if ($nextToken->type === Token::PUNCTUATOR && $token->value === '##') {
if (\count($argMap) > $argIdx) {
$newToken = $newToken->next = new Token($token->type, $token->value, $token->file);
}
$token = Token::skipWhitespace($nextToken);
}
}
if ($token->type === Token::IDENTIFIER && array_key_exists($token->value, $argMap)) {
$arg = $argMap[$token->value];
$toAdd = $toAddNext = new Token(Token::OTHER, '', 'computed');
Expand Down
73 changes: 40 additions & 33 deletions lib/PreProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ public function process(string $header): array {
$tokens = $this->resolveInclude($line, $directive->file);
$lines = array_merge($tokens, $lines);
break;
case 'include_next':
$tokens = $this->resolveInclude($line, $directive->file, true);
$lines = array_merge($tokens, $lines);
break;
case 'define':
if (empty($line)) {
throw new \LogicException("#define must have a name");
Expand Down Expand Up @@ -100,19 +104,33 @@ public function process(string $header): array {
break;
case 'else':
case 'elif':
$lines = $this->skipIf($lines);
$lines = $this->skipIf($lines, true);
break;
case 'endif':
// ignore
break;
case 'pragma':
if (empty($line)) {
throw new \LogicException("At least one declaration is required for pragma");
}
$pragmaMode = $line;
if ($pragmaMode->value === "once") {
if (Token::skipWhitespace($pragmaMode->next)) {
throw new \LogicException("pragma once has no further arguments");
}
$this->headers[$pragmaMode->file] = true;
}
break;
case 'warning':
// ignore
break;
case 'error':
var_dump($this->context);
//var_dump($this->context);
//var_dump(array_keys($this->context->getDefines()));
$this->debug($directive);
throw new \LogicException('We reached an error preprocessor token:');
default:
var_dump($line);
var_dump($directive->value);
throw new \LogicException("Unknown directive found {$directive->value}");
}
Expand Down Expand Up @@ -173,6 +191,7 @@ private function skipIf(array $lines, bool $skipAll = false): array {
return $lines;
case 'define':
case 'include':
case 'pragma': // there are no special pragmas supposed to be in #if's
case 'undef':
case 'error':
case 'warning':
Expand All @@ -187,15 +206,19 @@ private function skipIf(array $lines, bool $skipAll = false): array {
return [];
}

private function findAndParse(string $header, string $contextDir, string $contextFile): array {
private function findAndParse(string $header, string $contextDir, string $contextFile, bool $next = false): array {
$contextDir = rtrim($contextDir, '/');
$file = $this->findHeaderFile($header, $contextDir, $contextFile);
$file = $this->findHeaderFile($header, $contextDir, $contextFile, $next);
if (($this->headers[$file] ?? false) === true) { // has pragma once
return [];
}
$this->headers[$file] = false;
$code = file_get_contents($file);
$lines = $this->parser->parse($file, $code);
return $lines;
}

private function resolveInclude(?Token $arg, string $contextFile): array {
private function resolveInclude(?Token $arg, string $contextFile, bool $next = false): array {
$contextDir = dirname($contextFile);
if (empty($arg)) {
throw new \LogicException("Empty include declaration");
Expand All @@ -204,9 +227,9 @@ private function resolveInclude(?Token $arg, string $contextFile): array {
if ($type->type === Token::LITERAL) {
$file = $type->value;
if (!empty($args)) {
throw new \LogicException("extra tokens in #include directive");
throw new \LogicException("extra tokens in #include" . ($next ? "_next" : "") . " directive");
}
return $this->findAndParse($file, $contextDir, $contextFile);
return $this->findAndParse($file, $contextDir, $contextFile, $next);
} elseif ($type->type === Token::PUNCTUATOR && $type->value === '<' && !empty($arg->next)) {
// handle <> include:
$file = '';
Expand All @@ -218,44 +241,28 @@ private function resolveInclude(?Token $arg, string $contextFile): array {
$file .= $arg->value;
}
if (!empty($args)) {
throw new \LogicException("extra tokens in #include directive");
throw new \LogicException("extra tokens in #include" . ($next ? "_next" : "") . " directive");
}
// always a system import
return $this->findAndParse($file, $contextDir, $contextFile);
return $this->findAndParse($file, $contextDir, $contextFile, $next);
}
var_dump($type, $arg);
throw new \LogicException("Illegal include directive");
}

private function findHeaderFile(string $header, string $contextDir, string $contextFile): string {
if ($header[0] === '/' || ($header[1] === ':' && $header[2] === '\\')) {
if (file_exists($header)) {
return $header;
}
} else {
if ($contextDir) {
$dir = $contextDir;
while (!empty($dir) && $dir !== '/') {
$file = "$dir/$header";
if (file_exists($file)) {
return $file;
}
$dir = dirname($dir);
}
}
foreach ($this->context->headerSearchPaths as $path) {
$test = $path . '/' . $header;
if (file_exists($test)) {
return $test;
}
}
}
private function findHeaderFile(string $header, string $contextDir, string $contextFile, bool $next): string {
if ($headerFile = $this->context->findHeaderFile($header, $contextDir, $contextFile, $next)) {
return $headerFile;
}
var_dump($this->context->headerSearchPaths);
throw new \LogicException("Could not find header file: $header given context $contextDir (called from $contextFile)");
}

private function debug(?Token $token): void {
echo "T: ";
if ($token) {
echo "@{$token->file} :";
}
while ($token !== null) {
echo $token->value . ' ';
$token = $token->next;
Expand Down Expand Up @@ -401,4 +408,4 @@ public function nextArg(): void {
$this->args[] = $this->currentArg->next;
$this->currentArg = new Token(0, '', 'internal');
}
}
}
16 changes: 16 additions & 0 deletions test/cases/c/elifchain.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Test for #elif chains
--FILE--

#if 0
#elif 1
#elif 0
#elif 0
#else
#error "ERROR"
#endif

int bar;

--EXPECT--
int bar;
11 changes: 11 additions & 0 deletions test/cases/c/pragma_once.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Test for #elif chains
--FILE--

#include "pragma_once.h"
#include "pragma_once.h"

int TEST;

--EXPECT--
int bar;
11 changes: 11 additions & 0 deletions test/generated/c/elifchainTest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

#if 0
#elif 1
#elif 0
#elif 0
#else
#error "ERROR"
#endif

int bar;

34 changes: 34 additions & 0 deletions test/generated/c/elifchainTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace PHPCParser\Test\c;
use PHPCParser\CParser;
use PHPCParser\Printer;
use PHPCParser\Printer\Dumper;
use PHPCParser\Printer\C;
use PHPUnit\Framework\TestCase;

/**
* Note: this is a generated file, do not edit this!!!
*/
class elifchainTest extends TestCase {

const EXPECTED = 'int bar;';

protected CParser $parser;
protected Printer $printer;

public function setUp(): void {
$this->parser = new CParser;
$this->parser->addSearchPath(__DIR__);
$this->parser->addSearchPath(__DIR__ . '/../../include');
$this->printer = new C;
}

/**
* @textdox Test for #elif chains
*/
public function testCode() {
$translationUnit = $this->parser->parse(__DIR__ . '/elifchainTest.c');
$actual = $this->printer->print($translationUnit);
$this->assertEquals(self::EXPECTED, trim($actual));
}
}
6 changes: 6 additions & 0 deletions test/generated/c/pragma_onceTest.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#include "pragma_once.h"
#include "pragma_once.h"

int TEST;

Loading