├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── grammar ├── grammar.y ├── parser.template.php ├── rebuild.php └── tokens.template.php ├── lib ├── CParser.php ├── Compiler.php ├── Context.php ├── Error.php ├── IR.php ├── IR │ ├── AbstractDeclarator.php │ ├── Declaration.php │ ├── Declarator.php │ ├── DirectAbstractDeclarator.php │ ├── DirectAbstractDeclarator │ │ ├── AbstractDeclarator.php │ │ ├── Array_.php │ │ ├── CompleteArray.php │ │ ├── Function_.php │ │ └── IncompleteArray.php │ ├── DirectDeclarator.php │ ├── DirectDeclarator │ │ ├── Array_.php │ │ ├── CompleteArray.php │ │ ├── Declarator.php │ │ ├── Function_.php │ │ ├── Identifier.php │ │ └── IncompleteArray.php │ ├── FieldDeclaration.php │ ├── InitDeclarator.php │ └── QualifiedPointer.php ├── Lexer.php ├── Node.php ├── Node │ ├── Asm │ │ ├── GotoLabels.php │ │ ├── Operand.php │ │ ├── Operands.php │ │ └── Registers.php │ ├── Decl.php │ ├── Decl │ │ ├── NamedDecl.php │ │ ├── NamedDecl │ │ │ ├── TypeDecl.php │ │ │ ├── TypeDecl │ │ │ │ ├── TagDecl.php │ │ │ │ ├── TagDecl │ │ │ │ │ ├── EnumDecl.php │ │ │ │ │ └── RecordDecl.php │ │ │ │ ├── TypedefNameDecl.php │ │ │ │ └── TypedefNameDecl │ │ │ │ │ └── TypedefDecl.php │ │ │ ├── ValueDecl.php │ │ │ └── ValueDecl │ │ │ │ ├── DeclaratorDecl.php │ │ │ │ ├── DeclaratorDecl │ │ │ │ ├── FieldDecl.php │ │ │ │ ├── FunctionDecl.php │ │ │ │ ├── VarDecl.php │ │ │ │ └── VarDecl │ │ │ │ │ ├── ExternVarDecl.php │ │ │ │ │ └── ParamVarDecl.php │ │ │ │ └── EnumConstantDecl.php │ │ ├── Specifiers │ │ │ ├── Attribute.php │ │ │ └── AttributeList.php │ │ ├── ValueDecl.php │ │ └── ValueDecl │ │ │ └── BindingDecl.php │ ├── DeclContext.php │ ├── DeclGroup.php │ ├── Stmt.php │ ├── Stmt │ │ ├── AsmStmt.php │ │ ├── BreakStmt.php │ │ ├── CompoundStmt.php │ │ ├── ContinueStmt.php │ │ ├── DeclStmt.php │ │ ├── DoLoopStmt.php │ │ ├── EmptyStmt.php │ │ ├── GotoStmt.php │ │ ├── IfStmt.php │ │ ├── Label.php │ │ ├── Label │ │ │ ├── CaseLabel.php │ │ │ ├── DefaultLabel.php │ │ │ └── IdentifiedLabel.php │ │ ├── LoopStmt.php │ │ ├── ReturnStmt.php │ │ ├── SwitchStmt.php │ │ ├── ValueStmt.php │ │ └── ValueStmt │ │ │ ├── Expr.php │ │ │ └── Expr │ │ │ ├── AbstractConditionalOperator.php │ │ │ ├── AbstractConditionalOperator │ │ │ └── ConditionalOperator.php │ │ │ ├── BinaryOperator.php │ │ │ ├── CallExpr.php │ │ │ ├── CastExpr.php │ │ │ ├── DeclRefExpr.php │ │ │ ├── DimFetchExpr.php │ │ │ ├── FloatLiteral.php │ │ │ ├── FuncName.php │ │ │ ├── Initializer │ │ │ ├── InitializerDimension.php │ │ │ ├── InitializerElement.php │ │ │ ├── InitializerStructRef.php │ │ │ └── NamedInitializer.php │ │ │ ├── InitializerExpr.php │ │ │ ├── IntegerLiteral.php │ │ │ ├── StringLiteral.php │ │ │ ├── StructDerefExpr.php │ │ │ ├── StructRefExpr.php │ │ │ ├── TypeRefExpr.php │ │ │ └── UnaryOperator.php │ ├── TranslationUnitDecl.php │ ├── Type.php │ └── Type │ │ ├── ArbitraryAttributedType.php │ │ ├── ArrayType.php │ │ ├── ArrayType │ │ ├── CompleteArrayType.php │ │ ├── ConstantArrayType.php │ │ ├── IncompleteArrayType.php │ │ └── VariableArrayType.php │ │ ├── AttributedType.php │ │ ├── BuiltinType.php │ │ ├── DecltypeType.php │ │ ├── ExplicitAttributedType.php │ │ ├── FunctionType.php │ │ ├── FunctionType │ │ └── FunctionProtoType.php │ │ ├── ParenType.php │ │ ├── PointerType.php │ │ ├── TagType.php │ │ ├── TagType │ │ ├── EnumType.php │ │ └── RecordType.php │ │ ├── TypeWithKeyword.php │ │ ├── TypeWithKeyword │ │ └── ElaboratedType.php │ │ └── TypedefType.php ├── NodeAbstract.php ├── Parser.php ├── ParserAbstract.php ├── PreProcessor.php ├── PreProcessor │ ├── Parser.php │ ├── Token.php │ └── Tokenizer.php ├── Printer.php ├── Printer │ ├── C.php │ └── Dumper.php ├── Scope.php └── Tokens.php ├── phpunit.xml.dist └── test ├── cases ├── c │ ├── basic_math.phpt │ ├── char_literals.phpt │ ├── elifchain.phpt │ ├── escape_sequences.phpt │ ├── expr1.phpt │ ├── expr2.phpt │ ├── function_pointers.phpt │ ├── functions.phpt │ ├── issue_2.phpt │ ├── pragma_once.phpt │ ├── preprocessor_concat.phpt │ ├── preprocessor_recursion.phpt │ ├── preprocessor_ternary.phpt │ ├── preprocessor_x_macro.phpt │ ├── struct.phpt │ ├── struct_pointers.phpt │ ├── typedef.phpt │ └── vars.phpt └── dump │ ├── bare_function_typedef.phpt │ ├── includes_and_typedefs.phpt │ └── struct.phpt ├── generated ├── c │ ├── basic_mathTest.c │ ├── basic_mathTest.php │ ├── elifchainTest.c │ ├── elifchainTest.php │ ├── escape_sequencesTest.c │ ├── escape_sequencesTest.php │ ├── expr1Test.c │ ├── expr1Test.php │ ├── function_pointersTest.c │ ├── function_pointersTest.php │ ├── functionsTest.c │ ├── functionsTest.php │ ├── issue_2Test.c │ ├── issue_2Test.php │ ├── pragma_onceTest.c │ ├── pragma_onceTest.php │ ├── preprocessor_concatTest.c │ ├── preprocessor_concatTest.php │ ├── preprocessor_ternaryTest.c │ ├── preprocessor_ternaryTest.php │ ├── structTest.c │ ├── structTest.php │ ├── struct_pointersTest.c │ ├── struct_pointersTest.php │ ├── typedefTest.c │ ├── typedefTest.php │ ├── varsTest.c │ └── varsTest.php └── dump │ ├── bare_function_typedefTest.c │ ├── bare_function_typedefTest.php │ ├── includes_and_typedefsTest.c │ ├── includes_and_typedefsTest.php │ ├── structTest.c │ └── structTest.php ├── include ├── includes_and_typedefs.h └── pragma_once.h └── rebuild.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Anthony Ferrara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHPCParser 2 | 3 | This is a library to parse C code into an AST. Using PHP. 4 | 5 | Yes, this is an extraordinarily bad idea... 6 | 7 | 8 | ## Example 9 | 10 | ```php 11 | $parser = new PHPCParser\CParser; 12 | 13 | $ast = $parser->parse('path/to/file'); 14 | ``` 15 | 16 | Note that pre-processor directives are all correctly resolved. 17 | 18 | If you need to set a pre-processor define, you can use a context; 19 | 20 | ```php 21 | $parser = new PHPCParser\CParser; 22 | 23 | $context = new PHPCParser\Context; 24 | // #define A 42 25 | $context->defineInt('A', 42); 26 | // #define B "testing" 27 | $context->defineString('B', "testing"); 28 | // #define C testing 29 | $context->defineIdentifier('C', 'testing'); 30 | // etc... 31 | 32 | $ast = $parser->parse('path/to/file', $context); 33 | ``` 34 | 35 | And that's all there is to it (until it is working that is...)... 36 | 37 | ## Generating AST from clang 38 | 39 | ```console 40 | $ clang -cc1 -ast-dump test.c 41 | ``` 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ircmaxell/php-c-parser", 3 | "description": "Parse C when using PHP", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Anthony Ferrara", 8 | "email": "ircmaxell@gmail.com" 9 | }, 10 | { 11 | "name": "Bob Weinand", 12 | "email": "bobwei9@hotmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=7.4" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^8.0", 20 | "ircmaxell/php-yacc": "dev-master" 21 | }, 22 | "autoload": { 23 | "psr-4": { 24 | "PHPCParser\\": "lib/" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /grammar/parser.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $stackPos-(%l-%n) 6 | #semval(%n,%t) $stackPos-(%l-%n) 7 | 8 | namespace PHPCParser; 9 | 10 | use PHPCParser\Node\Stmt\ValueStmt\Expr; 11 | 12 | #include; 13 | 14 | /* This is an automatically GENERATED file, which should not be manually edited. 15 | */ 16 | class Parser extends ParserAbstract 17 | { 18 | protected int $tokenToSymbolMapSize = #(YYMAXLEX); 19 | protected int $actionTableSize = #(YYLAST); 20 | protected int $gotoTableSize = #(YYGLAST); 21 | 22 | protected int $invalidSymbol = #(YYBADCH); 23 | protected int $errorSymbol = #(YYINTERRTOK); 24 | protected int $defaultAction = #(YYDEFAULT); 25 | protected int $unexpectedTokenRule = #(YYUNEXPECTED); 26 | 27 | protected int $YY2TBLSTATE = #(YY2TBLSTATE); 28 | protected int $numNonLeafStates = #(YYNLSTATES); 29 | 30 | protected array $symbolToName = array( 31 | #listvar terminals 32 | ); 33 | 34 | protected array $tokenToSymbol = array( 35 | #listvar yytranslate 36 | ); 37 | 38 | protected array $action = array( 39 | #listvar yyaction 40 | ); 41 | 42 | protected array $actionCheck = array( 43 | #listvar yycheck 44 | ); 45 | 46 | protected array $actionBase = array( 47 | #listvar yybase 48 | ); 49 | 50 | protected array $actionDefault = array( 51 | #listvar yydefault 52 | ); 53 | 54 | protected array $goto = array( 55 | #listvar yygoto 56 | ); 57 | 58 | protected array $gotoCheck = array( 59 | #listvar yygcheck 60 | ); 61 | 62 | protected array $gotoBase = array( 63 | #listvar yygbase 64 | ); 65 | 66 | protected array $gotoDefault = array( 67 | #listvar yygdefault 68 | ); 69 | 70 | protected array $ruleToNonTerminal = array( 71 | #listvar yylhs 72 | ); 73 | 74 | protected array $ruleToLength = array( 75 | #listvar yylen 76 | ); 77 | #if -t 78 | 79 | protected array $productions = array( 80 | #production-strings; 81 | ); 82 | #endif 83 | 84 | protected function initReduceCallbacks() { 85 | $this->reduceCallbacks = [ 86 | #reduce 87 | %n => function ($stackPos) { 88 | %b 89 | }, 90 | #noact 91 | %n => function ($stackPos) { 92 | $this->semValue = $this->semStack[$stackPos]; 93 | }, 94 | #endreduce 95 | ]; 96 | } 97 | } 98 | #tailcode; -------------------------------------------------------------------------------- /grammar/rebuild.php: -------------------------------------------------------------------------------- 1 | \'[^\\\\\']*+(?:\\\\.[^\\\\\']*+)*+\') 11 | (?"[^\\\\"]*+(?:\\\\.[^\\\\"]*+)*+") 12 | (?(?&singleQuotedString)|(?&doubleQuotedString)) 13 | (?/\*[^*]*+(?:\*(?!/)[^*]*+)*+\*/) 14 | (?\{[^\'"/{}]*+(?:(?:(?&string)|(?&comment)|(?&code)|/)[^\'"/{}]*+)*+}) 15 | )'; 16 | const PARAMS = '\[(?[^[\]]*+(?:\[(?¶ms)\][^[\]]*+)*+)\]'; 17 | const ARGS = '\((?[^()]*+(?:\((?&args)\)[^()]*+)*+)\)'; 18 | 19 | 20 | 21 | $generator = new Generator; 22 | 23 | $grammarCode = file_get_contents(__DIR__ . '/grammar.y'); 24 | 25 | $grammarCode = resolveCompiles($grammarCode); 26 | $grammarCode = resolveNodes($grammarCode); 27 | $grammarCode = resolveMacros($grammarCode); 28 | $grammarCode = resolveStackAccess($grammarCode); 29 | 30 | $errorFile = null; 31 | // $errorFile = fopen(__DIR__ . '/y.phpyacc.err', 'w'); 32 | $debugFile = null; 33 | // $debugFile = fopen(__DIR__ . '/y.phpyacc.out', 'w'); 34 | 35 | $generator->generate( 36 | new Context('grammar.y', $errorFile, $debugFile, true), 37 | $grammarCode, 38 | file_get_contents(__DIR__ . '/parser.template.php'), 39 | __DIR__ . '/../lib/Parser.php' 40 | ); 41 | 42 | $generator->generate( 43 | new Context('grammar.y'), 44 | $grammarCode, 45 | file_get_contents(__DIR__ . '/tokens.template.php'), 46 | __DIR__ . '/../lib/Tokens.php' 47 | ); 48 | 49 | 50 | 51 | 52 | /////////////////////////////// 53 | /// Preprocessing functions /// 54 | /////////////////////////////// 55 | function resolveNodes($code) { 56 | return preg_replace_callback( 57 | '~\b(?[A-Z][a-zA-Z_\\\\]++)\s*' . PARAMS . '~', 58 | function($matches) { 59 | // recurse 60 | $matches['params'] = resolveNodes($matches['params']); 61 | $params = magicSplit( 62 | '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', 63 | $matches['params'] 64 | ); 65 | $paramCode = ''; 66 | foreach ($params as $param) { 67 | $paramCode .= $param . ', '; 68 | } 69 | return 'new ' . $matches['name'] . '(' . $paramCode . 'attributes())'; 70 | }, 71 | $code 72 | ); 73 | } 74 | function resolveCompiles($code) { 75 | return preg_replace_callback( 76 | '~\b(?compile[a-zA-Z_]++)\s*' . PARAMS . '~', 77 | function($matches) { 78 | // recurse 79 | $matches['params'] = resolveNodes($matches['params']); 80 | $params = magicSplit( 81 | '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', 82 | $matches['params'] 83 | ); 84 | $paramCode = ''; 85 | foreach ($params as $param) { 86 | $paramCode .= $param . ', '; 87 | } 88 | return '$this->compiler->' . $matches['name'] . '(' . $paramCode . 'attributes())'; 89 | }, 90 | $code 91 | ); 92 | } 93 | function resolveMacros($code) { 94 | return preg_replace_callback( 95 | '~\b(?)(?!array\()(?[a-z][A-Za-z]++)' . ARGS . '~', 96 | function($matches) { 97 | // recurse 98 | $matches['args'] = resolveMacros($matches['args']); 99 | $name = $matches['name']; 100 | $args = magicSplit( 101 | '(?:' . PARAMS . '|' . ARGS . ')(*SKIP)(*FAIL)|,', 102 | $matches['args'] 103 | ); 104 | if ('attributes' == $name) { 105 | assertArgs(0, $args, $name); 106 | return '$this->startAttributeStack[#1] + $this->endAttributes'; 107 | } 108 | if ('stackAttributes' == $name) { 109 | assertArgs(1, $args, $name); 110 | return '$this->startAttributeStack[' . $args[0] . ']' 111 | . ' + $this->endAttributeStack[' . $args[0] . ']'; 112 | } 113 | if ('init' == $name) { 114 | return '$$ = array(' . implode(', ', $args) . ')'; 115 | } 116 | if ('push' == $name) { 117 | assertArgs(2, $args, $name); 118 | return $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0]; 119 | } 120 | if ('pushNormalizing' == $name) { 121 | assertArgs(2, $args, $name); 122 | return 'if (is_array(' . $args[1] . ')) { $$ = array_merge(' . $args[0] . ', ' . $args[1] . '); }' 123 | . ' else { ' . $args[0] . '[] = ' . $args[1] . '; $$ = ' . $args[0] . '; }'; 124 | } 125 | if ('toArray' == $name) { 126 | assertArgs(1, $args, $name); 127 | return 'is_array(' . $args[0] . ') ? ' . $args[0] . ' : array(' . $args[0] . ')'; 128 | } 129 | if ('parseVar' == $name) { 130 | assertArgs(1, $args, $name); 131 | return 'substr(' . $args[0] . ', 1)'; 132 | } 133 | if ('parseEncapsed' == $name) { 134 | assertArgs(3, $args, $name); 135 | return 'foreach (' . $args[0] . ' as $s) { if ($s instanceof Node\Scalar\EncapsedStringPart) {' 136 | . ' $s->value = Node\Scalar\String_::parseEscapeSequences($s->value, ' . $args[1] . ', ' . $args[2] . '); } }'; 137 | } 138 | if ('makeNop' == $name) { 139 | assertArgs(3, $args, $name); 140 | return '$startAttributes = ' . $args[1] . ';' 141 | . ' if (isset($startAttributes[\'comments\']))' 142 | . ' { ' . $args[0] . ' = new Stmt\Nop($startAttributes + ' . $args[2] . '); }' 143 | . ' else { ' . $args[0] . ' = null; }'; 144 | } 145 | if ('strKind' == $name) { 146 | assertArgs(1, $args, $name); 147 | return '(' . $args[0] . '[0] === "\'" || (' . $args[0] . '[1] === "\'" && ' 148 | . '(' . $args[0] . '[0] === \'b\' || ' . $args[0] . '[0] === \'B\')) ' 149 | . '? Scalar\String_::KIND_SINGLE_QUOTED : Scalar\String_::KIND_DOUBLE_QUOTED)'; 150 | } 151 | if ('prependLeadingComments' == $name) { 152 | assertArgs(1, $args, $name); 153 | return '$attrs = $this->startAttributeStack[#1]; $stmts = ' . $args[0] . '; ' 154 | . 'if (!empty($attrs[\'comments\'])) {' 155 | . '$stmts[0]->setAttribute(\'comments\', ' 156 | . 'array_merge($attrs[\'comments\'], $stmts[0]->getAttribute(\'comments\', []))); }'; 157 | } 158 | return $matches[0]; 159 | }, 160 | $code 161 | ); 162 | } 163 | function assertArgs($num, $args, $name) { 164 | if ($num != count($args)) { 165 | die('Wrong argument count for ' . $name . '().'); 166 | } 167 | } 168 | function resolveStackAccess($code) { 169 | $code = preg_replace('/\$\d+/', '$this->semStack[$0]', $code); 170 | $code = preg_replace('/#(\d+)/', '$$1', $code); 171 | return $code; 172 | } 173 | function removeTrailingWhitespace($code) { 174 | $lines = explode("\n", $code); 175 | $lines = array_map('rtrim', $lines); 176 | return implode("\n", $lines); 177 | } 178 | function ensureDirExists($dir) { 179 | if (!is_dir($dir)) { 180 | mkdir($dir, 0777, true); 181 | } 182 | } 183 | ////////////////////////////// 184 | /// Regex helper functions /// 185 | ////////////////////////////// 186 | function regex($regex) { 187 | return '~' . LIB . '(?:' . str_replace('~', '\~', $regex) . ')~'; 188 | } 189 | function magicSplit($regex, $string) { 190 | $pieces = preg_split(regex('(?:(?&string)|(?&comment)|(?&code))(*SKIP)(*FAIL)|' . $regex), $string); 191 | foreach ($pieces as &$piece) { 192 | $piece = trim($piece); 193 | } 194 | if ($pieces === ['']) { 195 | return []; 196 | } 197 | return $pieces; 198 | } 199 | -------------------------------------------------------------------------------- /grammar/tokens.template.php: -------------------------------------------------------------------------------- 1 | semValue 4 | #semval($,%t) $this->semValue 5 | #semval(%n) $this->stackPos-(%l-%n) 6 | #semval(%n,%t) $this->stackPos-(%l-%n) 7 | 8 | namespace PHPCParser; 9 | #include; 10 | 11 | /* GENERATED file based on grammar/grammar.y */ 12 | final class Tokens 13 | { 14 | #tokenval 15 | const T_%s = %n; 16 | #endtokenval 17 | } -------------------------------------------------------------------------------- /lib/CParser.php: -------------------------------------------------------------------------------- 1 | parser = new Parser(new Lexer); 17 | } 18 | 19 | public function parse(string $filename, ?Context $context = null): TranslationUnitDecl { 20 | // Create the preprocessor every time, since it shouldn't ever share state 21 | $this->context = $context ?? new Context($this->headerSearchPaths); 22 | $preprocessor = new PreProcessor($this->context); 23 | $tokens = $preprocessor->process($filename); 24 | return $this->parser->parse($tokens, $this->context); 25 | } 26 | 27 | public function getLastContext(): Context { 28 | return $this->context; 29 | } 30 | 31 | public function addSearchPath(string $path) { 32 | $this->headerSearchPaths[] = rtrim($path, '/'); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/Compiler.php: -------------------------------------------------------------------------------- 1 | scope = $scope; 21 | } 22 | 23 | /** @param Decl\Specifiers\AttributeList[] $attributeLists 24 | * @param Type[] $types 25 | * @param Declaration[] $declarations 26 | * @return Decl\NamedDecl\ValueDecl\DeclaratorDecl\FunctionDecl[] 27 | */ 28 | public function compileFunction(int $qualifiers, array $attributeLists, array $types, IR\Declarator $declarator, array $declarations, Stmt\CompoundStmt $stmts, array $attributes = []): array { 29 | $type = $this->compileType($types); 30 | $parts = $this->compileNamedDeclarator($declarator, $type); 31 | $name = $parts[0]; 32 | $signature = $parts[1]; 33 | $declaratorAsm = $parts[2]; 34 | if ($parts[3]) { 35 | $attributeLists[] = $parts[3]; 36 | } 37 | if ($qualifiers !== 0 || $attributeLists) { 38 | $signature = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $signature, $attributes); 39 | } 40 | if (empty($declarations)) { 41 | return [new Decl\NamedDecl\ValueDecl\DeclaratorDecl\FunctionDecl($name, $declaratorAsm, $signature, $stmts)]; 42 | } 43 | throw new \LogicException('Not implemented (yet)'); 44 | } 45 | 46 | /** @return Node\Decl[] */ 47 | public function compileExternalDeclaration(IR\Declaration $declaration, array $attributes = []): array { 48 | $qualifiers = $declaration->qualifiers; 49 | $attributeLists = $declaration->attributeLists; 50 | $type = $this->compileType($declaration->types); 51 | 52 | $result = []; 53 | if ($declaration->qualifiers & Decl::KIND_TYPEDEF) { 54 | // this is wrong 55 | foreach ($declaration->declarators as $declarator) { 56 | $result[] = $this->compileTypedef($declarator, $type, $attributes); 57 | } 58 | } elseif (empty($declaration->declarators)) { 59 | if ($type instanceof Type\TagType) { 60 | if ($type->decl->name !== null) { 61 | $this->scope->structdef($type->decl->name, $type->decl); 62 | } 63 | return [$type->decl]; 64 | } 65 | // Handle attributed types here too 66 | throw new \LogicException('Also not implemented yet'); 67 | } else { 68 | foreach ($declaration->declarators as $initDeclarator) { 69 | $declarator = $this->compileInitDeclarator($initDeclarator, $type, $attributes); 70 | $declarator->type = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $declarator->type, $attributes); 71 | $result[] = $declarator; 72 | } 73 | } 74 | return $result; 75 | } 76 | 77 | public function compileDeclarationStmt(IR\Declaration $declaration, array $attributes = []): Stmt\DeclStmt { 78 | $qualifiers = $declaration->qualifiers; 79 | $attributeLists = $declaration->attributeLists; 80 | $type = $this->compileType($declaration->types); 81 | 82 | if ($declaration->qualifiers & Decl::KIND_TYPEDEF) { 83 | throw new \LogicException("No typedefs inside statement blocks"); 84 | } elseif (empty($declaration->declarators)) { 85 | throw new \LogicException('No struct/enum defs inside statement blocks'); 86 | } else { 87 | $declGroup = new DeclGroup([], $attributes); 88 | foreach ($declaration->declarators as $initDeclarator) { 89 | $declarator = $this->compileInitDeclarator($initDeclarator, $type, $attributes); 90 | $declarator->type = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $declarator->type, $attributes); 91 | $declGroup->addDecl($declarator); 92 | } 93 | return new Stmt\DeclStmt($declGroup, $attributes); 94 | } 95 | } 96 | 97 | /** @param Decl\Specifiers\AttributeList[] $attributeLists 98 | * @param Type[] $types 99 | * @param FieldDeclaration[] $declarators 100 | * @return Decl\NamedDecl\ValueDecl\DeclaratorDecl\FieldDecl[] 101 | */ 102 | public function compileStructField(int $qualifiers, array $attributeLists, array $types, ?array $declarators, array $attributes = []): array { 103 | $result = []; 104 | $type = $this->compileType($types); 105 | if (is_null($declarators)) { 106 | return [new Node\Decl\NamedDecl\ValueDecl\DeclaratorDecl\FieldDecl(null, $type, null, $attributes)]; 107 | } 108 | foreach ($declarators as $fieldDeclarator) { 109 | if ($fieldDeclarator->declarator) { 110 | $parts = $this->compileNamedDeclarator($fieldDeclarator->declarator, $type); 111 | } else { 112 | $parts = [null, $type, null, null]; 113 | } 114 | if ($parts[3]) { 115 | $attributeLists[] = $parts[3]; 116 | } 117 | $result[] = new Decl\NamedDecl\ValueDecl\DeclaratorDecl\FieldDecl($parts[0], $parts[1], $fieldDeclarator->bitfieldSize, $attributes); 118 | } 119 | return $result; 120 | } 121 | 122 | /** @param Decl\Specifiers\AttributeList[] $attributeLists 123 | * @param Type[] $types 124 | */ 125 | public function compileParamVarDeclaration(int $qualifiers, array $attributeLists, array $types, IR\Declarator $declarator, array $attributes = []): Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl { 126 | $type = $this->compileType($types); 127 | $parts = $this->compileNamedDeclarator($declarator, $type); 128 | if ($parts[3]) { 129 | $attributeLists[] = $parts[3]; 130 | } 131 | if ($qualifiers !== 0 || $attributeLists) { 132 | $parts[1] = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $parts[1], $attributes); 133 | } 134 | return new Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl($parts[0], $parts[2], $parts[1], $attributes); 135 | } 136 | 137 | /** @param Decl\Specifiers\AttributeList[] $attributeLists 138 | * @param Type[] $types 139 | */ 140 | public function compileParamAbstractDeclaration(int $qualifiers, array $attributeLists, array $types, ?IR\AbstractDeclarator $declarator, array $attributes = []): Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl { 141 | $type = $this->compileType($types); 142 | if ($declarator !== null) { 143 | $type = $this->compileAbstractDeclarator($declarator, $type); 144 | } 145 | if ($qualifiers !== 0 || $attributeLists) { 146 | $type = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $type, $attributes); 147 | } 148 | return new Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl(null, null, $type, $attributes); 149 | } 150 | 151 | /** @param Decl\Specifiers\AttributeList[] $attributeLists 152 | * @param Type[] $types 153 | */ 154 | public function compileTypeReference(int $qualifiers, array $attributeLists, array $types, ?IR\AbstractDeclarator $declarator, array $attributes = []): Expr\TypeRefExpr { 155 | $type = $this->compileType($types); 156 | if ($declarator !== null) { 157 | $type = $this->compileAbstractDeclarator($declarator, $type); 158 | } 159 | if ($qualifiers !== 0 || $attributeLists) { 160 | $type = Type\AttributedType::fromDecl($qualifiers, $attributeLists, $type, $attributes); 161 | } 162 | return new Expr\TypeRefExpr($type); 163 | } 164 | 165 | /** @param Type[] $types */ 166 | public function compileType(array $types): Type { 167 | restart: 168 | if (empty($types)) { 169 | throw new \LogicException('Cannot compile empty type list'); 170 | } 171 | if (count($types) === 1) { 172 | return $types[0]; 173 | } 174 | if ($types[0] instanceof Type\BuiltinType && $types[1] instanceof Type\BuiltinType) { 175 | // combine in order 176 | $first = array_shift($types); 177 | $types[0] = new Type\BuiltinType($first->name . ' ' . $types[0]->name, $first->getAttributes()); 178 | goto restart; 179 | } elseif ($types[0] instanceof Type\BuiltinType && $types[1] instanceof Type\TypedefType) { 180 | $first = array_shift($types); 181 | $types[0] = new Type\BuiltinType($first->name . ' ' . $types[0]->name, $first->getAttributes()); 182 | goto restart; 183 | } else { 184 | if (!($types[0] instanceof Type\TypedefType && $types[1] instanceof Type\TypedefType)) { 185 | var_dump($types); 186 | throw new \LogicException("Unexpected typedef"); 187 | } 188 | // this can happen when typedef'ing already existing typedefs or having special typedefs for basic types like "typedef __SIZE_TYPE__ size_t;" 189 | // in case the left operand is a primitive type, skip this. Otherwise error out that its a redefinition 190 | if ($this->scope->isBuiltinType($types[count($types) - 1]->name)) { 191 | return $types[count($types) - 1]; 192 | } 193 | 194 | var_dump($types); 195 | throw new \LogicException("typedef {$types[1]->name} cannot be redefined as {$types[0]->name}"); 196 | } 197 | } 198 | 199 | public function compileQualifiedPointer(IR\QualifiedPointer $pointer, Type $type): Type { 200 | restart: 201 | $type = new Type\PointerType($type); 202 | if ($pointer->qualification > 0) { 203 | $type = Type\AttributedType::fromDecl($pointer->qualification, $pointer->attributeList, $type); 204 | } 205 | if ($pointer->parent !== null) { 206 | $pointer = $pointer->parent; 207 | goto restart; 208 | } 209 | return $type; 210 | } 211 | 212 | public function compileTypedef(IR\InitDeclarator $init, Type $type, array $attributes = []): Decl { 213 | if (!$init->initializer === null) { 214 | throw new \LogicException("Typedef cannot come with an initializer"); 215 | } 216 | $declarator = $init->declarator; 217 | return $this->compileTypedefDeclarator($declarator, $type, $attributes); 218 | } 219 | 220 | public function verifyTypeIsFunctionType(Type $type): bool { 221 | while ($type instanceof Type\ParenType) { 222 | $type = $type->parent; 223 | } 224 | while ($type instanceof Type\TypedefType) { 225 | $type = $this->scope->tryType($type->name); 226 | while ($type instanceof Type\ParenType) { 227 | $type = $type->parent; 228 | } 229 | } 230 | return $type instanceof Type\FunctionType\FunctionProtoType; 231 | } 232 | 233 | public function compileInitDeclarator(IR\InitDeclarator $initDeclarator, Type $type, array $attributes = []): Decl\NamedDecl\ValueDecl\DeclaratorDecl { 234 | $parts = $this->compileNamedDeclarator($initDeclarator->declarator, $type, $attributes); 235 | if ($this->verifyTypeIsFunctionType($parts[1])) { 236 | return new Decl\NamedDecl\ValueDecl\DeclaratorDecl\FunctionDecl($parts[0], $parts[2], $parts[1], null, $attributes); 237 | } 238 | return new Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl($parts[0], $parts[2], $parts[1], $initDeclarator->initializer, $attributes); 239 | } 240 | 241 | public function compileTypedefDeclarator(IR\Declarator $declarator, Type $type, array $attributes = []): Decl { 242 | $parts = $this->compileNamedDeclarator($declarator, $type, $attributes); 243 | $this->scope->typedef($parts[0], $parts[1]); 244 | return new TypedefDecl($parts[0], $parts[1], $attributes); 245 | } 246 | 247 | public function compileAbstractDeclarator(IR\AbstractDeclarator $declarator, Type $type): Type { 248 | restart: 249 | if ($declarator->pointer !== null) { 250 | $type = $this->compileQualifiedPointer($declarator->pointer, $type); 251 | } 252 | $directabstractdeclarator = $declarator->declarator; 253 | restart_direct: 254 | if (is_null($directabstractdeclarator)) { 255 | return $type; 256 | } elseif ($directabstractdeclarator instanceof IR\DirectAbstractDeclarator\AbstractDeclarator) { 257 | $type = new Type\ParenType($type, $directabstractdeclarator->attributes); 258 | $declarator = $directabstractdeclarator->declarator; 259 | goto restart; 260 | } elseif ($directabstractdeclarator instanceof IR\DirectAbstractDeclarator\Function_) { 261 | $params = $paramNames = []; 262 | foreach ($directabstractdeclarator->params as $param) { 263 | $params[] = $param->type; 264 | $paramNames[] = $param->name; 265 | } 266 | $type = new Type\FunctionType\FunctionProtoType($type, $params, $paramNames, $directabstractdeclarator->isVariadic, $directabstractdeclarator->attributeList, $directabstractdeclarator->attributes); 267 | $directabstractdeclarator = $directabstractdeclarator->declarator; 268 | goto restart_direct; 269 | } elseif ($directabstractdeclarator instanceof IR\DirectAbstractDeclarator\Array_) { 270 | if ($directabstractdeclarator instanceof IR\DirectAbstractDeclarator\IncompleteArray) { 271 | $type = new Type\ArrayType\IncompleteArrayType($type, $directabstractdeclarator->modifiers, $directabstractdeclarator->attributeList, $directabstractdeclarator->attributes); 272 | } elseif ($directabstractdeclarator instanceof IR\DirectAbstractDeclarator\CompleteArray) { 273 | if ($directabstractdeclarator->size->isConstant()) { 274 | $type = new Type\ArrayType\ConstantArrayType($type, $directabstractdeclarator->size, $directabstractdeclarator->modifiers, $directabstractdeclarator->attributeList, $directabstractdeclarator->attributes); 275 | } else { 276 | $type = new Type\ArrayType\VariableArrayType($type, $directabstractdeclarator->size, $directabstractdeclarator->modifiers, $directabstractdeclarator->attributeList, $directabstractdeclarator->attributes); 277 | } 278 | } 279 | $directabstractdeclarator = $directabstractdeclarator->declarator; 280 | goto restart_direct; 281 | } 282 | var_dump($directabstractdeclarator); 283 | throw new \LogicException('AbstractDeclarator not fully implemented yet'); 284 | } 285 | 286 | /** @return array{string, Type, null|string, Decl\Specifiers\AttributeList[]} */ 287 | public function compileNamedDeclarator(IR\Declarator $declarator, Type $type): array { 288 | restart: 289 | if ($declarator->pointer !== null) { 290 | $type = $this->compileQualifiedPointer($declarator->pointer, $type); 291 | } 292 | $directdeclarator = $declarator->declarator; 293 | $declaratorAsm = $directdeclarator->declaratorAsm; 294 | $attributeList = $directdeclarator->attributeList; 295 | restart_direct: 296 | if ($directdeclarator instanceof IR\DirectDeclarator\Identifier) { 297 | return [$directdeclarator->name, $type, $declaratorAsm, $attributeList]; 298 | } elseif ($directdeclarator instanceof IR\DirectDeclarator\Array_) { 299 | if ($directdeclarator instanceof IR\DirectDeclarator\IncompleteArray) { 300 | $type = new Type\ArrayType\IncompleteArrayType($type, $directdeclarator->modifiers, $directdeclarator->attributeList, $directdeclarator->attributes); 301 | } elseif ($directdeclarator instanceof IR\DirectDeclarator\CompleteArray) { 302 | if ($directdeclarator->size->isConstant()) { 303 | $type = new Type\ArrayType\ConstantArrayType($type, $directdeclarator->size, $directdeclarator->modifiers, $directdeclarator->attributeList, $directdeclarator->attributes); 304 | } else { 305 | $type = new Type\ArrayType\VariableArrayType($type, $directdeclarator->size, $directdeclarator->modifiers, $directdeclarator->attributeList, $directdeclarator->attributes); 306 | } 307 | } 308 | $directdeclarator = $directdeclarator->declarator; 309 | goto restart_direct; 310 | } elseif ($directdeclarator instanceof IR\DirectDeclarator\Declarator) { 311 | $type = new Type\ParenType($type); 312 | $declarator = $directdeclarator->declarator; 313 | goto restart; 314 | } elseif ($directdeclarator instanceof IR\DirectDeclarator\Function_) { 315 | $type = new Type\FunctionType\FunctionProtoType( 316 | $type, 317 | $this->compileDirectParamTypes(...$directdeclarator->params), 318 | $this->compileDirectParamTypeNames(...($directdeclarator->params)), 319 | $directdeclarator->isVariadic, 320 | $directdeclarator->attributeList 321 | ); 322 | $directdeclarator = $directdeclarator->name; 323 | goto restart_direct; 324 | } 325 | var_dump($directdeclarator); 326 | throw new \LogicException("Unknown declarator found for typedef"); 327 | } 328 | 329 | /** @return Type[] */ 330 | public function compileDirectParamTypes(Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl ... $params): array { 331 | $result = []; 332 | foreach ($params as $param) { 333 | $result[] = $param->type; 334 | } 335 | return $result; 336 | } 337 | 338 | /** @return string[] */ 339 | public function compileDirectParamTypeNames(Decl\NamedDecl\ValueDecl\DeclaratorDecl\VarDecl\ParamVarDecl ... $params): array { 340 | $result = []; 341 | foreach ($params as $param) { 342 | $result[] = $param->name; 343 | } 344 | return $result; 345 | } 346 | } -------------------------------------------------------------------------------- /lib/Error.php: -------------------------------------------------------------------------------- 1 | rawMessage = $message; 22 | if (is_array($attributes)) { 23 | $this->attributes = $attributes; 24 | } else { 25 | $this->attributes = ['startLine' => $attributes]; 26 | } 27 | $this->updateMessage(); 28 | } 29 | 30 | public function getStartLine() : int { 31 | return $this->attributes['startLine'] ?? -1; 32 | } 33 | 34 | public function setStartLine(int $line): void { 35 | $this->attributes['startLine'] = $line; 36 | $this->updateMessage(); 37 | } 38 | 39 | public function getSourceToken() : Token { 40 | return $this->attributes['sourceToken'] ?? new Token(0, "", "", -1); 41 | } 42 | 43 | public function setSourceToken(string $line): void { 44 | $this->attributes['sourceToken'] = $line; 45 | $this->updateMessage(); 46 | } 47 | 48 | protected function updateMessage() { 49 | $this->message = $this->rawMessage; 50 | if (-1 === $this->getStartLine()) { 51 | $this->message .= ' on unknown line'; 52 | } else { 53 | $this->message .= ' on line ' . $this->getStartLine(); 54 | } 55 | if (($token = $this->getSourceToken())->line !== -1) { 56 | $origins = ["{$token->file}:{$token->line}"]; 57 | $originToken = $token; 58 | do { 59 | foreach ($originToken->origin as $origin) { 60 | $origins[] = "{$origin->file}:{$origin->line}"; 61 | } 62 | } while ($originToken = \reset($originToken->origin)); 63 | $this->message .= ' (originating from ' . implode(" <- ", array_unique($origins)) . ')'; 64 | } 65 | if (isset($this->attributes['dump'])) { 66 | $this->message .= " with dumped data: "; 67 | foreach ($this->attributes['dump'] as $dump) { 68 | ob_start(); 69 | var_dump($dump); 70 | $this->message .= "\n" . ob_get_clean(); 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /lib/IR.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /lib/IR/AbstractDeclarator.php: -------------------------------------------------------------------------------- 1 | pointer = $pointer; 16 | $this->declarator = $declarator; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/IR/Declaration.php: -------------------------------------------------------------------------------- 1 | qualifiers = $qualifiers; 24 | $this->types = $types; 25 | $this->declarators = $declarators; 26 | $this->attributeLists = $attributeLists; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/IR/Declarator.php: -------------------------------------------------------------------------------- 1 | pointer = $pointer; 16 | $this->declarator = $declarator; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/IR/DirectAbstractDeclarator.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/IR/DirectAbstractDeclarator/Array_.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 18 | $this->modifiers = $modifiers; 19 | $this->attributeList = $attributeList; 20 | } 21 | } -------------------------------------------------------------------------------- /lib/IR/DirectAbstractDeclarator/CompleteArray.php: -------------------------------------------------------------------------------- 1 | size = $size; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/IR/DirectAbstractDeclarator/Function_.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 23 | $this->params = $params; 24 | $this->isVariadic = $isVariadic; 25 | $this->attributeList = $attributeList; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/IR/DirectAbstractDeclarator/IncompleteArray.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 16 | $this->modifiers = $modifiers; 17 | $this->attributeList = $attributeList; 18 | } 19 | } -------------------------------------------------------------------------------- /lib/IR/DirectDeclarator/CompleteArray.php: -------------------------------------------------------------------------------- 1 | size = $size; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/IR/DirectDeclarator/Declarator.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/IR/DirectDeclarator/Function_.php: -------------------------------------------------------------------------------- 1 | name = $name; 18 | $this->params = $params; 19 | $this->isVariadic = $isVariadic; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/IR/DirectDeclarator/Identifier.php: -------------------------------------------------------------------------------- 1 | name = $name; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/IR/DirectDeclarator/IncompleteArray.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 17 | $this->bitfieldSize = $bitfieldSize; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/IR/InitDeclarator.php: -------------------------------------------------------------------------------- 1 | declarator = $declarator; 16 | $this->initializer = $initializer; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/IR/QualifiedPointer.php: -------------------------------------------------------------------------------- 1 | qualification = $qualification; 19 | $this->attributeList = $attributeList; 20 | $this->parent = $parent; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/Lexer.php: -------------------------------------------------------------------------------- 1 | tokens = $tokens; 18 | $this->tokenPos = -1; 19 | $this->currentToken = null; 20 | $this->scope = $scope; 21 | } 22 | 23 | public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null): int { 24 | $startAttributes = []; 25 | $endAttributes = []; 26 | 27 | while(true) { 28 | if ($this->currentToken === null) { 29 | if (!array_key_exists(++$this->tokenPos, $this->tokens)) { 30 | $value = "\0"; 31 | return 0; 32 | } 33 | 34 | $this->currentToken = $this->tokens[$this->tokenPos]; 35 | continue; 36 | } else { 37 | $currentStartToken = $this->currentToken; 38 | $token = $this->extractToken(); 39 | if ($token === null) { 40 | // tells us to go to the next token 41 | continue; 42 | } 43 | } 44 | $startAttributes['sourceToken'] = $currentStartToken; 45 | $startAttributes['startLine'] = $this->tokenPos + 1; 46 | 47 | if (is_string($token)) { 48 | $id = ord($token); 49 | $value = $token; 50 | } else { 51 | $value = $token[1]; 52 | $id = $token[0]; 53 | } 54 | return $id; 55 | } 56 | throw new \LogicException("Reached the end of lexer loop, should never happen"); 57 | } 58 | 59 | /** @return array{int, string}|null */ 60 | private function extractToken(): ?array { 61 | if ($this->currentToken->type === Token::IDENTIFIER) { 62 | return $this->extractIdentifier(); 63 | } elseif ($this->currentToken->type === Token::NUMBER) { 64 | return $this->extractNumber(); 65 | } elseif ($this->currentToken->type === Token::LITERAL) { 66 | return $this->extractLiteral(); 67 | } elseif ($this->currentToken->type === Token::PUNCTUATOR) { 68 | return $this->extractPunctuation(); 69 | } elseif ($this->currentToken->type === Token::WHITESPACE) { 70 | $this->currentToken = $this->currentToken->next; 71 | return null; 72 | } elseif ($this->currentToken->type === Token::OTHER) { 73 | if ($this->currentToken->value === '') { 74 | $this->currentToken = $this->currentToken->next; 75 | return null; 76 | } 77 | return $this->extractOther(); 78 | } 79 | throw new \LogicException("Unknown token type encountered: {$this->currentToken->type}"); 80 | } 81 | 82 | /** @return array{int, string} */ 83 | private function extractNumber(): array { 84 | $number = $this->currentToken->value; 85 | $this->currentToken = $this->currentToken->next; 86 | // TODO: fix this 87 | if (ctype_digit($number)) { 88 | return [Tokens::T_I_CONSTANT, $number]; 89 | } 90 | if (strpos($number, '.') !== false) { 91 | return [Tokens::T_F_CONSTANT, $number]; 92 | } 93 | return [Tokens::T_I_CONSTANT, $number]; 94 | } 95 | 96 | /** @var string[] */ 97 | private array $literalsBuffer = []; 98 | /** @return array{int, string}|null */ 99 | private function extractLiteral(): ?array { 100 | $string = $this->currentToken->value; 101 | $this->currentToken = $this->currentToken->next; 102 | $tokenLine = $this->tokenPos; 103 | $token = $this->currentToken; 104 | for (;;) { 105 | if ($token === null) { 106 | if (\count($this->tokens) > ++$tokenLine) { 107 | $token = $this->tokens[$tokenLine]; 108 | } else { 109 | break; 110 | } 111 | } elseif ($token->type === Token::WHITESPACE || $token->value === '') { 112 | $token = $token->next; 113 | } else { 114 | break; 115 | } 116 | } 117 | if ($token !== null && $token->type === Token::LITERAL) { 118 | $this->literalsBuffer[] = $string; 119 | return null; 120 | } 121 | if ($this->literalsBuffer) { 122 | $this->literalsBuffer[] = $string; 123 | $string = implode($this->literalsBuffer); 124 | $this->literalsBuffer = []; 125 | } 126 | return [Tokens::T_STRING_LITERAL, $string]; 127 | } 128 | 129 | /** @return array{int, string} */ 130 | private function extractPunctuation(): array { 131 | $value = $this->currentToken->value; 132 | $this->currentToken = $this->currentToken->next; 133 | switch ($value) { 134 | case '...': 135 | return [Tokens::T_ELLIPSIS, $value]; 136 | case '-': 137 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 138 | $this->currentToken = $this->currentToken->next; 139 | return [Tokens::T_SUB_ASSIGN, '-=']; 140 | } elseif ($this->currentToken !== null && $this->currentToken->value === '>') { 141 | $this->currentToken = $this->currentToken->next; 142 | return [Tokens::T_PTR_OP, '->']; 143 | } elseif ($this->currentToken !== null && $this->currentToken->value === '-') { 144 | $this->currentToken = $this->currentToken->next; 145 | return [Tokens::T_DEC_OP, '--']; 146 | } 147 | goto emit_single; 148 | case '+': 149 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 150 | $this->currentToken = $this->currentToken->next; 151 | return [Tokens::T_ADD_ASSIGN, '+=']; 152 | } elseif ($this->currentToken !== null && $this->currentToken->value === '+') { 153 | $this->currentToken = $this->currentToken->next; 154 | return [Tokens::T_INC_OP, '++']; 155 | } 156 | goto emit_single; 157 | case '*': 158 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 159 | $this->currentToken = $this->currentToken->next; 160 | return [Tokens::T_MUL_ASSIGN, '*=']; 161 | } 162 | goto emit_single; 163 | case '%': 164 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 165 | $this->currentToken = $this->currentToken->next; 166 | return [Tokens::T_MOD_ASSIGN, '%=']; 167 | } 168 | goto emit_single; 169 | case '/': 170 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 171 | $this->currentToken = $this->currentToken->next; 172 | return [Tokens::T_DIV_ASSIGN, '/=']; 173 | } 174 | goto emit_single; 175 | case '=': 176 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 177 | $this->currentToken = $this->currentToken->next; 178 | return [Tokens::T_EQ_OP, '==']; 179 | } 180 | goto emit_single; 181 | case '!': 182 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 183 | $this->currentToken = $this->currentToken->next; 184 | return [Tokens::T_NE_OP, '!=']; 185 | } 186 | goto emit_single; 187 | case '|': 188 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 189 | $this->currentToken = $this->currentToken->next; 190 | return [Tokens::T_OR_ASSIGN, '|=']; 191 | } elseif ($this->currentToken !== null && $this->currentToken->value === '|') { 192 | $this->currentToken = $this->currentToken->next; 193 | return [Tokens::T_OR_OP, '||']; 194 | } 195 | goto emit_single; 196 | case '&': 197 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 198 | $this->currentToken = $this->currentToken->next; 199 | return [Tokens::T_AND_ASSIGN, '&=']; 200 | } elseif ($this->currentToken !== null && $this->currentToken->value === '&') { 201 | $this->currentToken = $this->currentToken->next; 202 | return [Tokens::T_AND_OP, '&&']; 203 | } 204 | goto emit_single; 205 | case '^': 206 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 207 | $this->currentToken = $this->currentToken->next; 208 | return [Tokens::T_XOR_ASSIGN, '^=']; 209 | } 210 | goto emit_single; 211 | case '>': 212 | if ($this->currentToken !== null && $this->currentToken->value === '>') { 213 | $this->currentToken = $this->currentToken->next; 214 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 215 | $this->currentToken = $this->currentToken->next; 216 | return [Tokens::T_RIGHT_ASSIGN, '>>=']; 217 | } 218 | return [Tokens::T_RIGHT_OP, '>>']; 219 | } elseif ($this->currentToken !== null && $this->currentToken->value === '=') { 220 | $this->currentToken = $this->currentToken->next; 221 | return [Tokens::T_GE_OP, '>=']; 222 | } 223 | goto emit_single; 224 | case '<': 225 | if ($this->currentToken !== null && $this->currentToken->value === '<') { 226 | $this->currentToken = $this->currentToken->next; 227 | if ($this->currentToken !== null && $this->currentToken->value === '=') { 228 | $this->currentToken = $this->currentToken->next; 229 | return [Tokens::T_LEFT_ASSIGN, '<<=']; 230 | } 231 | return [Tokens::T_LEFT_OP, '<<']; 232 | } elseif ($this->currentToken !== null && $this->currentToken->value === '=') { 233 | $this->currentToken = $this->currentToken->next; 234 | return [Tokens::T_LE_OP, '<=']; 235 | } 236 | goto emit_single; 237 | case ';': 238 | case '(': 239 | case '~': 240 | case ')': 241 | case '{': 242 | case '}': 243 | case ',': 244 | case '[': 245 | case ']': 246 | case '?': 247 | case '.': 248 | case ':': 249 | emit_single: 250 | return [ord($value), $value]; 251 | } 252 | throw new \LogicException("Unsure how to extract unknown punctuator '$value'"); 253 | } 254 | 255 | private const IDENTIFIER_MAP = [ 256 | 'asm' => Tokens::T_ASM, 257 | '__asm' => Tokens::T_ASM, 258 | '__asm__' => Tokens::T_ASM, 259 | '__attribute__' => Tokens::T_ATTRIBUTE, 260 | 'auto' => Tokens::T_AUTO, 261 | 'break' => Tokens::T_BREAK, 262 | 'case' => Tokens::T_CASE, 263 | 'char' => Tokens::T_CHAR, 264 | 'const' => Tokens::T_CONST, 265 | 'continue' => Tokens::T_CONTINUE, 266 | 'default' => Tokens::T_DEFAULT, 267 | 'do' => Tokens::T_DO, 268 | 'double' => Tokens::T_DOUBLE, 269 | 'else' => Tokens::T_ELSE, 270 | 'enum' => Tokens::T_ENUM, 271 | 'extern' => Tokens::T_EXTERN, 272 | 'float' => Tokens::T_FLOAT, 273 | 'for' => Tokens::T_FOR, 274 | 'goto' => Tokens::T_GOTO, 275 | 'if' => Tokens::T_IF, 276 | 'inline' => Tokens::T_INLINE, 277 | '__inline' => Tokens::T_INLINE, 278 | '__inline__' => Tokens::T_INLINE, 279 | 'int' => Tokens::T_INT, 280 | 'long' => Tokens::T_LONG, 281 | '__int128' => Tokens::T_INT128, 282 | '__float128' => Tokens::T_FLOAT128, 283 | 'register' => Tokens::T_REGISTER, 284 | 'restrict' => Tokens::T_RESTRICT, 285 | '__restrict' => Tokens::T_RESTRICT, 286 | 'return' => Tokens::T_RETURN, 287 | 'short' => Tokens::T_SHORT, 288 | 'signed' => Tokens::T_SIGNED, 289 | 'sizeof' => Tokens::T_SIZEOF, 290 | 'static' => Tokens::T_STATIC, 291 | 'struct' => Tokens::T_STRUCT, 292 | 'switch' => Tokens::T_SWITCH, 293 | 'typedef' => Tokens::T_TYPEDEF, 294 | 'union' => Tokens::T_UNION, 295 | 'unsigned' => Tokens::T_UNSIGNED, 296 | 'void' => Tokens::T_VOID, 297 | 'volatile' => Tokens::T_VOLATILE, 298 | '__volatile__' => Tokens::T_VOLATILE, 299 | 'while' => Tokens::T_WHILE, 300 | '_Alignas' => Tokens::T_ALIGNAS, 301 | '_Alignof' => Tokens::T_ALIGNOF, 302 | '_Atomic' => Tokens::T_ATOMIC, 303 | '_Bool' => Tokens::T_BOOL, 304 | '_Complex' => Tokens::T_COMPLEX, 305 | '_Generic' => Tokens::T_GENERIC, 306 | '_Imaginary' => Tokens::T_IMAGINARY, 307 | '_Noreturn' => Tokens::T_NORETURN, 308 | '_Static_assert' => Tokens::T_STATIC_ASSERT, 309 | '_Thread_local' => Tokens::T_THREAD_LOCAL, 310 | '__thread' => Tokens::T_THREAD_LOCAL, 311 | '__func__' => Tokens::T_FUNC_NAME, 312 | ]; 313 | 314 | /** @return array{int, string}|null */ 315 | private function extractIdentifier(): ?array { 316 | $tok = $this->currentToken; 317 | $this->currentToken = $this->currentToken->next; 318 | 319 | if (isset(self::IDENTIFIER_MAP[$tok->value])) { 320 | return [self::IDENTIFIER_MAP[$tok->value], $tok->value]; 321 | } 322 | // This is a no-op keyword in gcc, just skip it, is has no value for us 323 | if ($tok->value === '__extension__') { 324 | return null; 325 | } 326 | return [$this->scope->lookup($tok->value), $tok->value]; 327 | } 328 | } -------------------------------------------------------------------------------- /lib/Node.php: -------------------------------------------------------------------------------- 1 | clobberMode = $clobberMode; 16 | $this->variable = $variable; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return ['clobberMode', 'variable']; 21 | } 22 | } -------------------------------------------------------------------------------- /lib/Node/Asm/Operands.php: -------------------------------------------------------------------------------- 1 | name = $name; 18 | $this->fields = $fields; 19 | } 20 | 21 | public function getSubNodeNames(): array { 22 | return ['name', 'fields']; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/TypeDecl/TagDecl/RecordDecl.php: -------------------------------------------------------------------------------- 1 | kind = $kind; 22 | $this->name = $name; 23 | $this->fields = $fields; 24 | $this->attributeList = $attributeList; 25 | } 26 | 27 | public function getSubNodeNames(): array { 28 | return ['kind', 'name', 'fields', 'attributeList']; 29 | } 30 | 31 | public function getType(): string { 32 | return 'Decl_NamedDecl_TypeDecl_TagDecl_RecordDecl'; 33 | } 34 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/TypeDecl/TypedefNameDecl.php: -------------------------------------------------------------------------------- 1 | name = $name; 18 | $this->type = $type; 19 | } 20 | 21 | public function getSubNodeNames(): array { 22 | return ['name', 'type']; 23 | } 24 | 25 | public function getType(): string { 26 | return 'Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl'; 27 | } 28 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/ValueDecl.php: -------------------------------------------------------------------------------- 1 | name = $name; 20 | $this->type = $type; 21 | $this->bitfieldSize = $bitfieldSize; 22 | } 23 | 24 | public function getSubNodeNames(): array { 25 | return ['name', 'type', 'bitfieldSize']; 26 | } 27 | 28 | public function getType(): string { 29 | return 'Decl_NamedDecl_ValueDecl_DeclaratorDecl_FieldDecl'; 30 | } 31 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/ValueDecl/DeclaratorDecl/FunctionDecl.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->declaratorAsm = $declaratorAsm; 22 | $this->type = $type; 23 | $this->stmts = $stmts; 24 | } 25 | 26 | public function getSubNodeNames(): array { 27 | return ['name', 'type', 'stmts', 'declaratorAsm']; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/ValueDecl/DeclaratorDecl/VarDecl.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->declaratorAsm = $declaratorAsm; 22 | $this->type = $type; 23 | $this->initializer = $initializer; 24 | } 25 | 26 | public function getSubNodeNames(): array { 27 | return ['name', 'type', 'initializer', 'declaratorAsm']; 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /lib/Node/Decl/NamedDecl/ValueDecl/DeclaratorDecl/VarDecl/ExternVarDecl.php: -------------------------------------------------------------------------------- 1 | name = $name; 19 | $this->value = $value; 20 | $this->attributeList = $attributeList; 21 | } 22 | 23 | public function getSubNodeNames(): array { 24 | return ['name', 'value', 'attributeList']; 25 | } 26 | } -------------------------------------------------------------------------------- /lib/Node/Decl/Specifiers/Attribute.php: -------------------------------------------------------------------------------- 1 | attribute = $attribute; 16 | $this->expr = $expr; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return ['attribute', 'expr']; 21 | } 22 | 23 | public function getAttributeName() { 24 | $attr = $this->attribute; 25 | $len = \strlen($attr); 26 | if ($len > 4 && $attr[0] === '_' && $attr[1] === '_' && $attr[$len - 2] === '_' && $attr[$len - 1] === '_') { 27 | return substr($attr, 2, $len - 4); 28 | } 29 | return $attr; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /lib/Node/Decl/Specifiers/AttributeList.php: -------------------------------------------------------------------------------- 1 | attributeList = $attributeList; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['attributeList']; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/Node/Decl/ValueDecl.php: -------------------------------------------------------------------------------- 1 | addDecl(...$declarations); 16 | } 17 | 18 | public function addDecl(Decl ...$declarations): void { 19 | foreach ($declarations as $declaration) { 20 | $this->declarations[] = $declaration; 21 | } 22 | } 23 | 24 | public function getSubNodeNames(): array { 25 | return ['declarations']; 26 | } 27 | 28 | public function getType(): string { 29 | return 'DeclGroup'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/Node/Stmt.php: -------------------------------------------------------------------------------- 1 | asm = $asm; 24 | $this->outputOperands = $outputOperands; 25 | $this->inputOperands = $inputOperands; 26 | $this->registers = $registers; 27 | $this->gotoLabels = $gotoLabels; 28 | } 29 | 30 | public function getSubNodeNames(): array { 31 | return array_merge(parent::getSubNodeNames(), ['asm', 'outputOperands', 'inputOperands', 'registers', 'gotoLabels', 'modifiers']); 32 | } 33 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/BreakStmt.php: -------------------------------------------------------------------------------- 1 | stmts = $stmts; 16 | } 17 | 18 | public function getSubNodeNames(): array { 19 | return array_merge(parent::getSubNodeNames(), ['stmts']); 20 | } 21 | 22 | public function getType(): string { 23 | return 'Stmt_CompoundStmt'; 24 | } 25 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ContinueStmt.php: -------------------------------------------------------------------------------- 1 | declarations = $declarations; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['declarations']; 19 | } 20 | 21 | public function getType(): string { 22 | return 'Stmt_DeclStmt'; 23 | } 24 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/DoLoopStmt.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 17 | $this->loopStmt = $loopExpr; 18 | } 19 | 20 | public function getSubNodeNames(): array { 21 | return array_merge(parent::getSubNodeNames(), ['condition', 'loopStmt']); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/EmptyStmt.php: -------------------------------------------------------------------------------- 1 | label = $label; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return array_merge(parent::getSubNodeNames(), ['label']); 18 | } 19 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/IfStmt.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 18 | $this->trueStmt = $trueStmt; 19 | $this->falseStmt = $falseStmt; 20 | } 21 | 22 | public function getSubNodeNames(): array { 23 | return array_merge(parent::getSubNodeNames(), ['condition', 'trueStmt', 'falseStmt']); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/Label.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['expr']; 19 | } 20 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/Label/DefaultLabel.php: -------------------------------------------------------------------------------- 1 | label = $label; 17 | $this->attributeList = $attributeList; 18 | } 19 | 20 | public function getSubNodeNames(): array { 21 | return ['label', 'attributeList']; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/LoopStmt.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 19 | $this->initStmt = $initStmt; 20 | $this->loopExpr = $loopExpr; 21 | $this->loopStmt = $loopStmt; 22 | } 23 | 24 | public function getSubNodeNames(): array { 25 | return array_merge(parent::getSubNodeNames(), ['condition', 'initExpr', 'loopExpr', 'loopStmt']); 26 | } 27 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ReturnStmt.php: -------------------------------------------------------------------------------- 1 | result = $result; 16 | } 17 | 18 | public function getSubNodeNames(): array { 19 | return array_merge(parent::getSubNodeNames(), ['result']); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/SwitchStmt.php: -------------------------------------------------------------------------------- 1 | condition = $condition; 17 | $this->stmt = $stmt; 18 | } 19 | 20 | public function getSubNodeNames(): array { 21 | return array_merge(parent::getSubNodeNames(), ['condition', 'stmt']); 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt.php: -------------------------------------------------------------------------------- 1 | cond = $cond; 19 | $this->ifTrue = $ifTrue; 20 | $this->ifFalse = $ifFalse; 21 | } 22 | 23 | public function getSubNodeNames(): array { 24 | return array_merge(parent::getSubNodeNames(), ['cond', 'ifTrue', 'ifFalse']); 25 | } 26 | 27 | public function isConstant(): bool { 28 | return $this->cond->isConstant() ? $this->ifTrue->isConstant() && $this->ifFalse->isConstant() : false; 29 | } 30 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/BinaryOperator.php: -------------------------------------------------------------------------------- 1 | left = $left; 50 | $this->right = $right; 51 | $this->kind = $kind; 52 | } 53 | 54 | public function getSubNodeNames(): array { 55 | return array_merge(parent::getSubNodeNames(), ['left', 'right', 'kind']); 56 | } 57 | 58 | public function isConstant(): bool { 59 | return $this->left->isConstant() && $this->right->isConstant(); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/CallExpr.php: -------------------------------------------------------------------------------- 1 | fn = $fn; 17 | $this->args = $args; 18 | } 19 | 20 | public function getSubNodeNames(): array { 21 | return array_merge(parent::getSubNodeNames(), ['fn', 'args']); 22 | } 23 | 24 | public function isConstant(): bool { 25 | return false; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/CastExpr.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 15 | $this->type = $type; 16 | } 17 | 18 | public function getSubNodeNames(): array { 19 | return array_merge(parent::getSubNodeNames(), ['expr', 'type']); 20 | } 21 | 22 | public function isConstant(): bool { 23 | return $this->expr->isConstant(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/DeclRefExpr.php: -------------------------------------------------------------------------------- 1 | name = $name; 16 | $this->decl = $decl; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return array_merge(parent::getSubNodeNames(), ['name', 'decl']); 21 | } 22 | 23 | public function isConstant(): bool { 24 | if ($this->decl instanceof Decl\NamedDecl\ValueDecl\EnumConstantDecl) { 25 | return true; 26 | } 27 | if ($this->decl === null) { 28 | return false; // TODO accessing non-extern const values in the current translation unit is also constant 29 | } 30 | throw new \LogicException('Unknown decl reference type'); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/DimFetchExpr.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 16 | $this->dimension = $dimension; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return array_merge(parent::getSubNodeNames(), ['expr', 'dimension']); 21 | } 22 | 23 | public function isConstant(): bool { 24 | return $this->expr->isConstant() && $this->dimension->isConstant(); 25 | } 26 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/FloatLiteral.php: -------------------------------------------------------------------------------- 1 | value = $value; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return array_merge(parent::getSubNodeNames(), ['value']); 18 | } 19 | 20 | public function isConstant(): bool { 21 | return true; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/FuncName.php: -------------------------------------------------------------------------------- 1 | dimension = $dimension; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['expr']; 19 | } 20 | 21 | public function isConstant(): bool { 22 | return $this->dimension->isConstant(); 23 | } 24 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/Initializer/InitializerElement.php: -------------------------------------------------------------------------------- 1 | designators = $designators; 17 | $this->expr = $expr; 18 | } 19 | 20 | public function isConstant(): bool { 21 | foreach ($this->designators as $designator) { 22 | if (!$designator->isConstant()) { 23 | return false; 24 | } 25 | } 26 | return $this->expr->isConstant(); 27 | } 28 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/Initializer/InitializerStructRef.php: -------------------------------------------------------------------------------- 1 | memberName = $memberName; 13 | } 14 | 15 | public function getSubNodeNames(): array { 16 | return ['memberName']; 17 | } 18 | 19 | public function isConstant(): bool { 20 | return true; 21 | } 22 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/Initializer/NamedInitializer.php: -------------------------------------------------------------------------------- 1 | initializers = $initializers; 18 | $this->explicitType = $explicitType; 19 | } 20 | 21 | public function getSubNodeNames(): array { 22 | return array_merge(parent::getSubNodeNames(), ['initializers', 'explicitType']); 23 | } 24 | 25 | public function isConstant(): bool { 26 | foreach ($this->initializers as $initializer) { 27 | if (!$initializer->isConstant()) { 28 | return false; 29 | } 30 | } 31 | return true; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/IntegerLiteral.php: -------------------------------------------------------------------------------- 1 | value = $value; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return array_merge(parent::getSubNodeNames(), ['value']); 18 | } 19 | 20 | public function isConstant(): bool { 21 | return true; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/StringLiteral.php: -------------------------------------------------------------------------------- 1 | value = $value; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return array_merge(parent::getSubNodeNames(), ['value']); 18 | } 19 | 20 | public function isConstant(): bool { 21 | return true; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/StructDerefExpr.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 16 | $this->memberName = $memberName; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return array_merge(parent::getSubNodeNames(), ['expr', 'memberName']); 21 | } 22 | 23 | public function isConstant(): bool { 24 | return false; 25 | } 26 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/StructRefExpr.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 15 | $this->memberName = $memberName; 16 | } 17 | 18 | public function getSubNodeNames(): array { 19 | return array_merge(parent::getSubNodeNames(), ['expr', 'memberName']); 20 | } 21 | 22 | public function isConstant(): bool { 23 | return $this->expr->isConstant(); 24 | } 25 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/TypeRefExpr.php: -------------------------------------------------------------------------------- 1 | type = $type; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return array_merge(parent::getSubNodeNames(), ['type']); 19 | } 20 | 21 | public function isConstant(): bool { 22 | return true; 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /lib/Node/Stmt/ValueStmt/Expr/UnaryOperator.php: -------------------------------------------------------------------------------- 1 | expr = $expr; 29 | $this->kind = $kind; 30 | } 31 | 32 | public function getSubNodeNames(): array { 33 | return array_merge(parent::getSubNodeNames(), ['expr', 'kind']); 34 | } 35 | 36 | public function isConstant(): bool { 37 | return $this->expr->isConstant(); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /lib/Node/TranslationUnitDecl.php: -------------------------------------------------------------------------------- 1 | attribute = $attribute; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return array_merge(parent::getSubNodeNames(), ['attribute']); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/Node/Type/ArrayType.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 18 | $this->modifiers = $modifiers; 19 | $this->attributeList = $attributeList; 20 | } 21 | 22 | public function getSubNodeNames(): array { 23 | return ['parent', 'modifiers', 'attributeList']; 24 | } 25 | } -------------------------------------------------------------------------------- /lib/Node/Type/ArrayType/CompleteArrayType.php: -------------------------------------------------------------------------------- 1 | size = $size; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return array_merge(parent::getSubNodeNames(), ['parent', 'size']); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/Node/Type/ArrayType/IncompleteArrayType.php: -------------------------------------------------------------------------------- 1 | size = $size; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return array_merge(parent::getSubNodeNames(), ['parent', 'size']); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/Node/Type/AttributedType.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 16 | } 17 | 18 | public function getSubNodeNames(): array { 19 | return ['parent']; 20 | } 21 | 22 | public static function fromDecl(int $kind, array $attributeLists, Type $parent, array $attributes = []): Type { 23 | if ($kind & Decl::KIND_TYPEDEF) { 24 | throw new \LogicException('Cannot compile typedef AttributedType'); 25 | } 26 | if ($kind & Decl::KIND_EXTERN) { 27 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_EXTERN, $parent, $attributes); 28 | } 29 | if ($kind & Decl::KIND_STATIC) { 30 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_STATIC, $parent, $attributes); 31 | } 32 | if ($kind & Decl::KIND_THREAD_LOCAL) { 33 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_THREAD_LOCAL, $parent, $attributes); 34 | } 35 | if ($kind & Decl::KIND_AUTO) { 36 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_AUTO, $parent, $attributes); 37 | } 38 | if ($kind & Decl::KIND_REGISTER) { 39 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_REGISTER, $parent, $attributes); 40 | } 41 | if ($kind & Decl::KIND_CONST) { 42 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_CONST, $parent, $attributes); 43 | } 44 | if ($kind & Decl::KIND_RESTRICT) { 45 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_RESTRICT, $parent, $attributes); 46 | } 47 | if ($kind & Decl::KIND_VOLATILE) { 48 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_VOLATILE, $parent, $attributes); 49 | } 50 | if ($kind & Decl::KIND_ATOMIC) { 51 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_ATOMIC, $parent, $attributes); 52 | } 53 | if ($kind & Decl::KIND_INLINE) { 54 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_INLINE, $parent, $attributes); 55 | } 56 | if ($kind & Decl::KIND_NORETURN) { 57 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_NORETURN, $parent, $attributes); 58 | } 59 | foreach ($attributeLists as $attributeList) { 60 | foreach ($attributeList->attributeList as $attribute) { 61 | switch ($attribute->getAttributeName()) { 62 | case "noreturn": 63 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_NORETURN, $parent, $attributes); 64 | break; 65 | case "always_inline": 66 | $parent = new ExplicitAttributedType(ExplicitAttributedType::KIND_ALWAYS_INLINE, $parent, $attributes); 67 | break; 68 | default: 69 | $parent = new ArbitraryAttributedType($attribute, $parent, $attributes); 70 | } 71 | } 72 | } 73 | return $parent; 74 | } 75 | 76 | } -------------------------------------------------------------------------------- /lib/Node/Type/BuiltinType.php: -------------------------------------------------------------------------------- 1 | name = $name; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return ['name']; 18 | } 19 | 20 | public function getType(): string { 21 | return 'Type_BuiltinType'; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Type/DecltypeType.php: -------------------------------------------------------------------------------- 1 | kind = $kind; 30 | } 31 | 32 | public function getSubNodeNames(): array { 33 | return array_merge(parent::getSubNodeNames(), ['kind']); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /lib/Node/Type/FunctionType.php: -------------------------------------------------------------------------------- 1 | return = $return; 26 | $this->params = $params; 27 | $this->paramNames = $paramNames; 28 | $this->isVariadic = $isVariadic; 29 | $this->attributeList = $attributeList; 30 | } 31 | 32 | public function getSubNodeNames(): array { 33 | return ['return', 'params', 'paramNames', 'isVariadic', 'attributeList']; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /lib/Node/Type/ParenType.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return ['parent']; 18 | } 19 | 20 | public function getType(): string { 21 | return 'Type_ParenType'; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Type/PointerType.php: -------------------------------------------------------------------------------- 1 | parent = $parent; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return ['parent']; 18 | } 19 | 20 | public function getType(): string { 21 | return 'Type_PointerType'; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/Node/Type/TagType.php: -------------------------------------------------------------------------------- 1 | decl = $decl; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['decl']; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/Node/Type/TagType/RecordType.php: -------------------------------------------------------------------------------- 1 | decl = $decl; 15 | } 16 | 17 | public function getSubNodeNames(): array { 18 | return ['decl']; 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /lib/Node/Type/TypeWithKeyword.php: -------------------------------------------------------------------------------- 1 | keyword = $keyword; 16 | $this->type = $type; 17 | } 18 | 19 | public function getSubNodeNames(): array { 20 | return ['keyword', 'type']; 21 | } 22 | 23 | public function getType(): string { 24 | return 'Type_TypeWithKeyword_ElaboratedType'; 25 | } 26 | } -------------------------------------------------------------------------------- /lib/Node/Type/TypedefType.php: -------------------------------------------------------------------------------- 1 | name = $name; 14 | } 15 | 16 | public function getSubNodeNames(): array { 17 | return ['name']; 18 | } 19 | 20 | public function getType(): string { 21 | return 'Type_TypedefType'; 22 | } 23 | } -------------------------------------------------------------------------------- /lib/NodeAbstract.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 14 | } 15 | /** 16 | * Gets line the node started in (alias of getStartLine). 17 | * 18 | * @return int Start line (or -1 if not available) 19 | */ 20 | public function getLine() : int { 21 | return $this->attributes['startLine'] ?? -1; 22 | } 23 | /** 24 | * Gets line the node started in. 25 | * 26 | * Requires the 'startLine' attribute to be enabled in the lexer (enabled by default). 27 | * 28 | * @return int Start line (or -1 if not available) 29 | */ 30 | public function getStartLine() : int { 31 | return $this->attributes['startLine'] ?? -1; 32 | } 33 | /** 34 | * Gets the line the node ended in. 35 | * 36 | * Requires the 'endLine' attribute to be enabled in the lexer (enabled by default). 37 | * 38 | * @return int End line (or -1 if not available) 39 | */ 40 | public function getEndLine() : int { 41 | return $this->attributes['endLine'] ?? -1; 42 | } 43 | /** 44 | * Gets the token offset of the first token that is part of this node. 45 | * 46 | * The offset is an index into the array returned by Lexer::getTokens(). 47 | * 48 | * Requires the 'startTokenPos' attribute to be enabled in the lexer (DISABLED by default). 49 | * 50 | * @return int Token start position (or -1 if not available) 51 | */ 52 | public function getStartTokenPos() : int { 53 | return $this->attributes['startTokenPos'] ?? -1; 54 | } 55 | /** 56 | * Gets the token offset of the last token that is part of this node. 57 | * 58 | * The offset is an index into the array returned by Lexer::getTokens(). 59 | * 60 | * Requires the 'endTokenPos' attribute to be enabled in the lexer (DISABLED by default). 61 | * 62 | * @return int Token end position (or -1 if not available) 63 | */ 64 | public function getEndTokenPos() : int { 65 | return $this->attributes['endTokenPos'] ?? -1; 66 | } 67 | /** 68 | * Gets the file offset of the first character that is part of this node. 69 | * 70 | * Requires the 'startFilePos' attribute to be enabled in the lexer (DISABLED by default). 71 | * 72 | * @return int File start position (or -1 if not available) 73 | */ 74 | public function getStartFilePos() : int { 75 | return $this->attributes['startFilePos'] ?? -1; 76 | } 77 | /** 78 | * Gets the file offset of the last character that is part of this node. 79 | * 80 | * Requires the 'endFilePos' attribute to be enabled in the lexer (DISABLED by default). 81 | * 82 | * @return int File end position (or -1 if not available) 83 | */ 84 | public function getEndFilePos() : int { 85 | return $this->attributes['endFilePos'] ?? -1; 86 | } 87 | /** 88 | * Gets all comments directly preceding this node. 89 | * 90 | * The comments are also available through the "comments" attribute. 91 | * 92 | * @return Comment[] 93 | */ 94 | public function getComments() : array { 95 | return $this->attributes['comments'] ?? []; 96 | } 97 | /** 98 | * Gets the doc comment of the node. 99 | * 100 | * The doc comment has to be the last comment associated with the node. 101 | * 102 | * @return null|Comment\Doc Doc comment object or null 103 | */ 104 | public function getDocComment() { 105 | $comments = $this->getComments(); 106 | if (!$comments) { 107 | return null; 108 | } 109 | $lastComment = $comments[count($comments) - 1]; 110 | if (!$lastComment instanceof Comment\Doc) { 111 | return null; 112 | } 113 | return $lastComment; 114 | } 115 | /** 116 | * Sets the doc comment of the node. 117 | * 118 | * This will either replace an existing doc comment or add it to the comments array. 119 | * 120 | * @param Comment\Doc $docComment Doc comment to set 121 | */ 122 | public function setDocComment(Comment\Doc $docComment) { 123 | $comments = $this->getComments(); 124 | $numComments = count($comments); 125 | if ($numComments > 0 && $comments[$numComments - 1] instanceof Comment\Doc) { 126 | // Replace existing doc comment 127 | $comments[$numComments - 1] = $docComment; 128 | } else { 129 | // Append new comment 130 | $comments[] = $docComment; 131 | } 132 | $this->setAttribute('comments', $comments); 133 | } 134 | public function setAttribute(string $key, $value) { 135 | $this->attributes[$key] = $value; 136 | } 137 | public function hasAttribute(string $key) : bool { 138 | return array_key_exists($key, $this->attributes); 139 | } 140 | public function getAttribute(string $key, $default = null) { 141 | if (array_key_exists($key, $this->attributes)) { 142 | return $this->attributes[$key]; 143 | } 144 | return $default; 145 | } 146 | public function getAttributes() : array { 147 | return $this->attributes; 148 | } 149 | public function setAttributes(array $attributes) { 150 | $this->attributes = $attributes; 151 | } 152 | /** 153 | * @return array 154 | */ 155 | public function jsonSerialize() : array { 156 | return ['nodeType' => $this->getType()] + get_object_vars($this); 157 | } 158 | 159 | /** @return string[] */ 160 | public function getSubNodeNames(): array { 161 | return []; 162 | } 163 | 164 | public function getType() : string { 165 | $class = get_class($this); 166 | $class = str_replace(Node::class, '', $class); 167 | $class = substr($class, 1); // remove leading \ 168 | $class = str_replace('\\', '_', $class); 169 | return preg_replace('(__+)', '_', $class); 170 | } 171 | } -------------------------------------------------------------------------------- /lib/PreProcessor/Parser.php: -------------------------------------------------------------------------------- 1 | tokenizer = $tokenizer ?? new Tokenizer; 13 | } 14 | 15 | /** @return Token[] */ 16 | public function parse(string $file, string $code): array { 17 | $lines = preg_split("(\r\n|\n|\r)", $code); 18 | $lines = $this->mergeContinuedLines($lines); 19 | $lines = $this->stripComments($lines); 20 | $lines = $this->stripEmptyLines($lines); 21 | $tokens = $this->tokenizer->tokenize($file, $lines); 22 | return $tokens; 23 | } 24 | 25 | /** @param string[] $lines 26 | * @return string[] 27 | */ 28 | private function stripEmptyLines(array $lines): array { 29 | foreach ($lines as $lineno => $line) { 30 | if ($line == "") { 31 | unset($lines[$lineno]); 32 | } 33 | } 34 | return $lines; 35 | } 36 | 37 | /** @param string[] $lines 38 | * @return string[] 39 | */ 40 | private function mergeContinuedLines(array $lines): array { 41 | $lineno = 0; 42 | $length = count($lines); 43 | 44 | while ($lineno < $length) { 45 | $buffer = &$lines[$lineno++]; 46 | while (substr($buffer, -1) === '\\') { 47 | $buffer = substr($buffer, 0, -1); 48 | if ($lineno < $length) { 49 | $buffer .= $lines[$lineno]; 50 | unset($lines[$lineno++]); 51 | } else { 52 | break; 53 | } 54 | } 55 | } 56 | return $lines; 57 | } 58 | 59 | /** @param string[] $lines 60 | * @return string[] 61 | */ 62 | private function stripComments(array $lines): array { 63 | $result = []; 64 | $pos = 0; 65 | $length = array_key_last($lines); 66 | 67 | while ($pos <= $length) { 68 | while (!isset($lines[$pos])) { 69 | ++$pos; 70 | } 71 | $buffer = $lines[$pos]; 72 | if (strpos($buffer, '//') === false && strpos($buffer, "/*") === false) { 73 | $result[$pos++] = $buffer; 74 | continue; 75 | } 76 | $subbuffer = &$result[$pos++]; 77 | $subbuffer = ''; 78 | $i = 0; 79 | $lineLength = strlen($buffer); 80 | while ($i < $lineLength) { 81 | $char = $buffer[$i++]; 82 | if ($char === '/' && $i < $lineLength) { 83 | if ($buffer[$i] === '/') { 84 | // Single line comment: kill entire line from here out 85 | break; 86 | } elseif ($buffer[$i] === '*') { 87 | // Consume until we find a */ 88 | $i++; 89 | while (true) { 90 | if ($i >= $lineLength) { 91 | if ($pos <= $length) { 92 | while (!isset($lines[$pos])) { 93 | ++$pos; 94 | } 95 | $buffer = $lines[$pos++]; 96 | $i = 0; 97 | $lineLength = strlen($buffer); 98 | continue; 99 | // Continue to handle empty lines gracefully 100 | } else { 101 | // syntax error, unterminated /* 102 | throw new \RuntimeException("Unterminated /*"); 103 | } 104 | } 105 | $char = $buffer[$i++]; 106 | if ($char === '*' && $i < $lineLength && $buffer[$i] === '/') { 107 | // Found */ 108 | $i++; 109 | break; 110 | } 111 | } 112 | } else { 113 | $subbuffer .= $char; 114 | } 115 | } elseif ($char === '"') { 116 | // Todo: handle string literals 117 | $subbuffer .= $char; 118 | // Consume until we find an unescaped " 119 | while (true) { 120 | if ($i >= $lineLength) { 121 | if ($pos <= $length) { 122 | while (!isset($lines[$pos])) { 123 | ++$pos; 124 | } 125 | $buffer = $lines[$pos++]; 126 | $i = 0; 127 | $lineLength = strlen($buffer); 128 | continue; 129 | } else { 130 | throw new \RuntimeException("Unterminated \""); 131 | } 132 | } 133 | $char = $buffer[$i++]; 134 | if ($char === '\\') { 135 | $subbuffer .= $char; 136 | if ($i < $lineLength) { 137 | // Be sure to eat escaped character 138 | $subbuffer .= $buffer[$i++]; 139 | } 140 | } elseif ($char === '"') { 141 | // terminating " 142 | $subbuffer .= $char; 143 | break; 144 | } else { 145 | $subbuffer .= $char; 146 | } 147 | } 148 | } else { 149 | $subbuffer .= $char; 150 | } 151 | } 152 | } 153 | return $result; 154 | } 155 | } -------------------------------------------------------------------------------- /lib/PreProcessor/Token.php: -------------------------------------------------------------------------------- 1 | type = $type; 25 | $this->value = $value; 26 | $this->file = $file; 27 | $this->line = $line; 28 | $this->next = $next; 29 | } 30 | 31 | public function tail(): self { 32 | $node = $this; 33 | while (!is_null($node->next)) { 34 | $node = $node->next; 35 | } 36 | return $node; 37 | } 38 | 39 | public static function skipWhitespace(?self $node): ?self { 40 | while ($node !== null && $node->type === self::WHITESPACE) { 41 | $node = $node->next; 42 | } 43 | return $node; 44 | } 45 | 46 | } -------------------------------------------------------------------------------- /lib/PreProcessor/Tokenizer.php: -------------------------------------------------------------------------------- 1 | $line) { 16 | $result[$lineno + 1] = $this->tokenizeLine($file, $lineno + 1, $line); 17 | } 18 | return $result; 19 | } 20 | 21 | protected function convertEscapeSequences(string $str): string { 22 | return preg_replace_callback('(\\\\(?:(?[\\\\\'"?abfnrtve])|x(?[a-fA-F0-9]+)|(?[0-7]+)|(?s).+))', function ($m) { 23 | if ($m['chr'] !== "") { 24 | return [ 25 | 'a' => "\x7", 26 | 'b' => "\x8", 27 | 'f' => "\f", 28 | 'n' => "\n", 29 | 'r' => "\r", 30 | 't' => "\t", 31 | 'v' => "\v", 32 | 'e' => "\e", 33 | ][$m['chr']] ?? $m['chr']; 34 | } 35 | if ($m['hex'] !== "") { 36 | return \chr(intval($m['hex'], 16)); 37 | } 38 | if ($m['oct'] !== "") { 39 | return \chr(intval($m['oct'], 8)); 40 | } 41 | throw new \LogicException("Unknown character literal escape sequence: " . var_export($m[0], true)); 42 | }, $str); 43 | } 44 | 45 | protected function tokenizeLine(string $file, int $lineno, string $line): ?Token { 46 | $result = $first = new Token(0, '', $file); 47 | $length = strlen($line); 48 | $pos = 0; 49 | while ($pos < $length) { 50 | $char = $line[$pos++]; 51 | if (ctype_alpha($char) || $char === '_') { 52 | // identifier 53 | if ($char === 'L' && $pos < $length && $line[$pos] === "'") { 54 | ++$pos; 55 | goto single_quoted_string; 56 | } 57 | $buffer = $char; 58 | while ($pos < $length && (ctype_alnum($line[$pos]) || $line[$pos] === '_')) { 59 | $buffer .= $line[$pos++]; 60 | } 61 | $result = $result->next = new Token(Token::IDENTIFIER, $buffer, $file, $lineno); 62 | } elseif ($char === ' ' || $char === "\t" || $char === "\0") { 63 | // white space, ignore 64 | $buffer = $char; 65 | while ($pos < $length && ($line[$pos] === ' ' || $line[$pos] === "\t" || $line[$pos] === "\0")) { 66 | $buffer .= $line[$pos++]; 67 | } 68 | $result = $result->next = new Token(Token::WHITESPACE, $buffer, $file, $lineno); 69 | } elseif (ctype_digit($char) || ($char === '.' && $pos < $length && ctype_digit($line[$pos]))) { 70 | // Numeric literal 71 | $buffer = $char; 72 | $had_decimal_separator = false; 73 | while ($pos < $length) { 74 | $char = $line[$pos]; 75 | if ($char === 'e' || $char === 'E' || $char === 'p' || $char === 'P') { 76 | $buffer .= $char; 77 | $pos++; 78 | if ($pos < $length && ($line[$pos] === '-' || $line[$pos] === '+')) { 79 | // emit both 80 | $buffer .= $line[$pos++]; 81 | } 82 | } elseif (ctype_alnum($char) || (!$had_decimal_separator && $char === '.') || $char === '_') { 83 | $buffer .= $char; 84 | $had_decimal_separator = true; 85 | $pos++; 86 | } else { 87 | break; 88 | } 89 | } 90 | $result = $result->next = new Token(Token::NUMBER, $buffer, $file, $lineno); 91 | } elseif ($char === '"') { 92 | $buffer = ''; 93 | while ($pos < $length) { 94 | $char = $line[$pos++]; 95 | if ($char === '"') { 96 | break; 97 | } elseif ($char === '\\' && $pos < $length) { 98 | // eat both characters since it's an escape 99 | $char = $line[$pos++]; 100 | $buffer .= '\\' . $char; 101 | } else { 102 | $buffer .= $char; 103 | } 104 | } 105 | $result = $result->next = new Token(Token::LITERAL, $this->convertEscapeSequences($buffer), $file, $lineno); 106 | } elseif ($char === "'") { 107 | single_quoted_string: 108 | $buffer = ''; 109 | while ($pos < $length) { 110 | $char = $line[$pos++]; 111 | if ($char === "'") { 112 | break; 113 | } elseif ($char === '\\' && $pos < $length) { 114 | // eat both characters since it's an escape 115 | $char = $line[$pos++]; 116 | $buffer .= '\\' . $char; 117 | } else { 118 | $buffer .= $char; 119 | } 120 | } 121 | $buffer = $this->convertEscapeSequences($buffer); 122 | if (strlen($buffer) === 1) { 123 | $value = $buffer; 124 | } else { 125 | throw new \LogicException("Syntax error: unexpected illegal string literal found '$buffer' in $file at position $pos"); 126 | } 127 | $result = $result->next = new Token(Token::NUMBER, (string) \ord($value), $file, $lineno); 128 | } elseif (ctype_punct($char)) { 129 | if ($char === '.' && $pos + 1 < $length && $line[$pos] === '.' && $line[$pos + 1] === '.') { 130 | // special case for ... token 131 | $result = $result->next = new Token(Token::PUNCTUATOR, '...', $file, $lineno); 132 | $pos = $pos + 2; 133 | } elseif ($char === '@' || $char === '$' || $char === '`') { 134 | $result = $result->next = new Token(Token::OTHER, $char, $file, $lineno); 135 | } elseif ($char === '#' && $pos < $length && $line[$pos] === '#') { 136 | $result = $result->next = new Token(Token::PUNCTUATOR, '##', $file, $lineno); 137 | $pos++; 138 | } elseif ($char === '<' && $pos < $length && $line[$pos] === '%') { 139 | // Digraph 140 | $result = $result->next = new Token(Token::PUNCTUATOR, '{', $file, $lineno); 141 | $pos++; 142 | } elseif ($char === '%' && $pos < $length && $line[$pos] === '>') { 143 | // Digraph 144 | $result = $result->next = new Token(Token::PUNCTUATOR, '}', $file, $lineno); 145 | $pos++; 146 | } elseif ($char === '<' && $pos < $length && $line[$pos] === ':') { 147 | // Digraph 148 | $result = $result->next = new Token(Token::PUNCTUATOR, '[', $file, $lineno); 149 | $pos++; 150 | } elseif ($char === ':' && $pos < $length && $line[$pos] === '>') { 151 | // Digraph 152 | $result = $result->next = new Token(Token::PUNCTUATOR, ']', $file, $lineno); 153 | $pos++; 154 | } elseif ($char === '%' && $pos + 2 < $length && $line[$pos] === ':' && $line[$pos + 1] === '%' && $line[$pos + 2] === ':') { 155 | // Digraph 156 | $result = $result->next = new Token(Token::PUNCTUATOR, '##', $file, $lineno); 157 | $pos = $pos + 3; 158 | } elseif ($char === '%' && $pos < $length && $line[$pos] === ':') { 159 | // Digraph 160 | $result = $result->next = new Token(Token::PUNCTUATOR, '#', $file, $lineno); 161 | $pos++; 162 | } else { 163 | $result = $result->next = new Token(Token::PUNCTUATOR, $char, $file, $lineno); 164 | } 165 | } else { 166 | var_dump($char, ord($char), ord("\n")); 167 | die("Unknown Character in $file:$lineno"); 168 | } 169 | } 170 | return $first->next; 171 | } 172 | 173 | 174 | } 175 | 176 | -------------------------------------------------------------------------------- /lib/Printer.php: -------------------------------------------------------------------------------- 1 | printNode($node, 0); 14 | } 15 | 16 | /** @param Node[] $nodes */ 17 | public function printNodes(array $nodes, int $level): string { 18 | $result = ''; 19 | foreach ($nodes as $node) { 20 | $result .= str_repeat(' ', $level); 21 | if ($node instanceof Node) { 22 | $result .= $this->printNode($node, $level); 23 | } elseif (is_string($node)) { 24 | $result .= $node . "\n"; 25 | } 26 | } 27 | return $result; 28 | } 29 | 30 | public function printNode(Node $node, int $level): string { 31 | $result = $node->getType() . "\n"; 32 | foreach ($node->getSubnodeNames() as $name) { 33 | $result .= str_repeat(' ', $level + 1) . $name . ': '; 34 | $subNode = $node->$name; 35 | if ($subNode === null) { 36 | $result .= "null\n"; 37 | } elseif ($subNode instanceof Node) { 38 | $result .= $this->printNode($subNode, $level + 2); 39 | } elseif (is_string($subNode)) { 40 | $result .= '"' . $subNode . "\"\n"; 41 | } elseif (is_array($subNode)) { 42 | $result .= "[\n"; 43 | $result .= $this->printNodes($subNode, $level + 2); 44 | $result .= str_repeat(' ', $level + 1) . "]\n"; 45 | } elseif (is_int($subNode)) { 46 | $result .= $subNode . "\n"; 47 | } elseif (is_bool($subNode)) { 48 | $result .= ($subNode ? 'true' : 'false') . "\n"; 49 | } else { 50 | throw new \LogicException("Unknown subnode type encountered: " . gettype($subNode)); 51 | } 52 | } 53 | return $result; 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /lib/Scope.php: -------------------------------------------------------------------------------- 1 | Tokens::T_TYPEDEF_NAME, 9 | 'char' => Tokens::T_TYPEDEF_NAME, 10 | '_Bool' => Tokens::T_TYPEDEF_NAME, 11 | // 'int8_t' => Tokens::T_TYPEDEF_NAME, 12 | // 'uint8_t' => Tokens::T_TYPEDEF_NAME, 13 | // 'int16_t' => Tokens::T_TYPEDEF_NAME, 14 | // 'uint16_t' => Tokens::T_TYPEDEF_NAME, 15 | // 'int32_t' => Tokens::T_TYPEDEF_NAME, 16 | // 'uint32_t' => Tokens::T_TYPEDEF_NAME, 17 | // 'int64_t' => Tokens::T_TYPEDEF_NAME, 18 | // 'uint64_t' => Tokens::T_TYPEDEF_NAME, 19 | 'float' => Tokens::T_TYPEDEF_NAME, 20 | 'double' => Tokens::T_TYPEDEF_NAME, 21 | // 'uintptr_t' => Tokens::T_TYPEDEF_NAME, 22 | // 'intptr_t' => Tokens::T_TYPEDEF_NAME, 23 | 'size_t' => Tokens::T_TYPEDEF_NAME, 24 | // 'ssize_t' => Tokens::T_TYPEDEF_NAME, 25 | // 'ptrdiff_t' => Tokens::T_TYPEDEF_NAME, 26 | // 'off_t' => Tokens::T_TYPEDEF_NAME, 27 | // 'va_list' => Tokens::T_TYPEDEF_NAME, 28 | '__builtin_va_list' => Tokens::T_TYPEDEF_NAME, 29 | '__gnuc_va_list' => Tokens::T_TYPEDEF_NAME, 30 | // 'wchar_t' => Tokens::T_TYPEDEF_NAME, 31 | ]; 32 | 33 | /** @var Node\Type[] */ 34 | private array $types = []; 35 | /** @var Node\Decl[] */ 36 | private array $enums = []; 37 | /** @var Node\Decl[] */ 38 | private array $structs = []; 39 | 40 | private ?Scope $parent; 41 | 42 | public function __construct(?Scope $parent = null) { 43 | $this->parent = $parent; 44 | } 45 | 46 | public function isBuiltinType(string $identifier) { 47 | return isset($this->entries[$identifier]); 48 | } 49 | 50 | public function typedef(string $identifier, Node\Type $type): void { 51 | $this->entries[$identifier] = Tokens::T_TYPEDEF_NAME; 52 | $this->types[$identifier] = $type; 53 | } 54 | 55 | public function enumdef(string $identifier, Node\Decl $enum): void { 56 | $this->entries[$identifier] = Tokens::T_ENUMERATION_CONSTANT; 57 | $this->enums[$identifier] = $enum; 58 | } 59 | 60 | public function structdef(string $identifier, Node\Decl $struct): void { 61 | $this->structs[$identifier] = $struct; 62 | } 63 | 64 | public function lookup(string $identifier): int { 65 | if (isset($this->entries[$identifier])) { 66 | return $this->entries[$identifier]; 67 | } 68 | if ($this->parent !== null) { 69 | return $this->parent->lookup($identifier); 70 | } 71 | return Tokens::T_IDENTIFIER; 72 | } 73 | 74 | public function tryType(string $identifier): ?Node\Type { 75 | if (!isset($this->types[$identifier])) { 76 | if ($this->parent !== null) { 77 | return $this->parent->type($identifier); 78 | } 79 | return null; 80 | } 81 | return $this->types[$identifier]; 82 | } 83 | 84 | public function type(string $identifier): Node\Type { 85 | if ($type = $this->tryType($identifier)) { 86 | return $type; 87 | } 88 | throw new \LogicException("Attempt to lookup unknown type '$identifier'"); 89 | } 90 | 91 | public function enum(string $identifier): Node\Decl { 92 | if (!isset($this->enums[$identifier])) { 93 | if ($this->parent !== null) { 94 | return $this->parent->enum($identifier); 95 | } 96 | throw new \LogicException("Attempt to lookup unknown enum '$identifier'"); 97 | } 98 | return $this->enums[$identifier]; 99 | } 100 | 101 | public function struct(string $identifier): Node\Decl { 102 | if (!isset($this->structs[$identifier])) { 103 | if ($this->parent !== null) { 104 | return $this->parent->struct($identifier); 105 | } 106 | throw new \LogicException("Attempt to lookup unknown struct '$identifier'"); 107 | } 108 | return $this->structs[$identifier]; 109 | } 110 | } -------------------------------------------------------------------------------- /lib/Tokens.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | ./test 15 | 16 | 17 | 18 | 19 | 20 | ./lib/ 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/cases/c/basic_math.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic math operations 3 | --FILE-- 4 | 5 | static long add2(long a, long b) { 6 | return a + b; 7 | } 8 | 9 | static long add_and_sub(long a, long b) { 10 | return (a + b) + 2 - 1 - b; 11 | } 12 | 13 | static long mul_sub(long a, long b) { 14 | return a * b - 3; 15 | } 16 | 17 | --EXPECT-- 18 | 19 | static long add2(long a, long b) { 20 | return (a + b); 21 | } 22 | 23 | static long add_and_sub(long a, long b) { 24 | return ((((a + b) + 2) - 1) - b); 25 | } 26 | 27 | static long mul_sub(long a, long b) { 28 | return ((a * b) - 3); 29 | } -------------------------------------------------------------------------------- /test/cases/c/char_literals.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test unary operator on empty define 3 | --FILE-- 4 | 5 | #if L'\0' - 1 <= 0 6 | int success; 7 | #endif 8 | 9 | --EXPECT-- 10 | int success; 11 | -------------------------------------------------------------------------------- /test/cases/c/elifchain.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for #elif chains 3 | --FILE-- 4 | 5 | #if 0 6 | #elif 1 7 | #elif 0 8 | #elif 0 9 | #else 10 | #error "ERROR" 11 | #endif 12 | 13 | int bar; 14 | 15 | --EXPECT-- 16 | int bar; 17 | -------------------------------------------------------------------------------- /test/cases/c/escape_sequences.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test escape sequence printing 3 | --FILE-- 4 | 5 | char a[] = { 6 | '\0', 7 | '\n', 8 | '\"', 9 | '\\', 10 | '\"', 11 | '\x12', 12 | '\377', 13 | }; 14 | const char *str = "a'b\n\"\\\"\x12\377\1"; 15 | 16 | --EXPECT-- 17 | char a[] = {0, 10, 34, 92, 34, 18, 255}; 18 | char *str = "a'b\x0a\"\\\"\x12\xff\x01"; 19 | -------------------------------------------------------------------------------- /test/cases/c/expr1.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for bitshift 3 | --FILE-- 4 | 5 | #define _FORTIFY_SOURCE 1 6 | #define __OPTIMIZE__ 2 7 | #define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) 8 | 9 | #if defined _FORTIFY_SOURCE && _FORTIFY_SOURCE > 0 \ 10 | && __GNUC_PREREQ (4, 1) && defined __OPTIMIZE__ && __OPTIMIZE__ > 0 11 | int success; 12 | #endif 13 | 14 | --EXPECT-- 15 | int success; 16 | -------------------------------------------------------------------------------- /test/cases/c/expr2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test unary operator on empty define 3 | --FILE-- 4 | 5 | #define _POSIX_SOURCE 1 6 | #define _XOPEN_SOURCE 7 | 8 | #if ((!defined __STRICT_ANSI__ \ 9 | || (defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) >= 500)) \ 10 | && !defined _POSIX_SOURCE && !defined _POSIX_C_SOURCE) 11 | #else 12 | int success; 13 | #endif 14 | 15 | --EXPECT-- 16 | int success; 17 | -------------------------------------------------------------------------------- /test/cases/c/function_pointers.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of function pointers 3 | --FILE-- 4 | typedef void *(*test_func)(int arg1_name, char *arg2_name); 5 | 6 | typedef void A(void * b); 7 | typedef void *(*with_void)(void); 8 | --EXPECT-- 9 | typedef void *(*test_func)(int arg1_name, char *arg2_name); 10 | typedef void A(void *b); 11 | typedef void *(*with_void)(void); -------------------------------------------------------------------------------- /test/cases/c/functions.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of functions 3 | --FILE-- 4 | 5 | extern char** bar(char* arg1, int* arg2); 6 | static void foobar(void); 7 | 8 | --EXPECT-- 9 | 10 | extern char **bar(char *arg1, int *arg2); 11 | static void foobar(void); -------------------------------------------------------------------------------- /test/cases/c/issue_2.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for issue #2 3 | --FILE-- 4 | 5 | #ifndef TEST 6 | #define TEST 1 7 | #endif 8 | 9 | #if TEST 10 | int bar; 11 | #endif 12 | 13 | --EXPECT-- 14 | int bar; 15 | -------------------------------------------------------------------------------- /test/cases/c/pragma_once.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for #elif chains 3 | --FILE-- 4 | 5 | #include "pragma_once.h" 6 | #include "pragma_once.h" 7 | 8 | int TEST; 9 | 10 | --EXPECT-- 11 | int bar; 12 | -------------------------------------------------------------------------------- /test/cases/c/preprocessor_concat.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for preprocessor##concat 3 | --FILE-- 4 | 5 | typedef char int8; 6 | 7 | #define DEF(a, b, c, d) a##b ba ## c ## d 8 | DEF(int, 8, 1, r;) 9 | 10 | --EXPECT-- 11 | typedef char int8; 12 | int8 ba1r; 13 | -------------------------------------------------------------------------------- /test/cases/c/preprocessor_recursion.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test self-referential macros 3 | --FILE-- 4 | 5 | #define int int success 6 | 7 | int; 8 | 9 | --EXPECT-- 10 | int success; 11 | -------------------------------------------------------------------------------- /test/cases/c/preprocessor_ternary.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for #if A ? B : C 3 | --FILE-- 4 | 5 | #define A 1 6 | #define B 2 7 | #define C 3 8 | 9 | #if (A ? B : C) < 3 10 | #else 11 | #error "ERROR - 1" 12 | #endif 13 | 14 | #if !A ? B : C < 3 15 | #error "ERROR - 2" 16 | #endif 17 | 18 | #if !A ? B > 1 : !defined C 19 | #error "ERROR - 3" 20 | #endif 21 | 22 | #if A ? B > 1 ? C > 2 : A > 3 : defined C 23 | #error "ERROR - 4" 24 | #endif 25 | 26 | int bar; 27 | 28 | --EXPECT-- 29 | int bar; 30 | -------------------------------------------------------------------------------- /test/cases/c/preprocessor_x_macro.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test for nested expansion with macros 3 | --FILE-- 4 | 5 | enum AST { 6 | ZEND_AST_BINARY_OP 7 | }; 8 | 9 | #define ZEND_EXPAND_VA(code) code 10 | #define ZEND_AST_SPEC_CALL_EX(name, ...) \ 11 | ZEND_EXPAND_VA(ZEND_AST_SPEC_CALL_EX_(name, __VA_ARGS__, _5, _4, _3, _2, _1, _0)(__VA_ARGS__)) 12 | #define ZEND_AST_SPEC_CALL_EX_(name, _, _6, _5, _4, _3, _2, _1, suffix, ...) \ 13 | name ## suffix 14 | 15 | #define zend_ast_create_ex(...) \ 16 | ZEND_AST_SPEC_CALL_EX(zend_ast_create_ex, __VA_ARGS__) 17 | 18 | static int *zend_ast_create_binary_op(int opcode, int *op0, int *op1) { 19 | return zend_ast_create_ex(ZEND_AST_BINARY_OP, opcode, op0, op1); 20 | } 21 | --EXPECT-- 22 | enum AST { 23 | ZEND_AST_BINARY_OP, 24 | }; 25 | static int *zend_ast_create_binary_op(int opcode, int *op0, int *op1) { 26 | return zend_ast_create_ex_2(ZEND_AST_BINARY_OP, opcode, op0, op1); 27 | } 28 | -------------------------------------------------------------------------------- /test/cases/c/struct.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of structs 3 | --FILE-- 4 | struct foo { 5 | int x, y:2, :6; 6 | float z; 7 | }; 8 | 9 | typedef int foo; 10 | 11 | typedef struct foo bar; 12 | --EXPECT-- 13 | struct foo { 14 | int x; 15 | int y :2; 16 | int :6; 17 | float z; 18 | }; 19 | typedef int foo; 20 | typedef struct foo bar; -------------------------------------------------------------------------------- /test/cases/c/struct_pointers.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of struct pointers 3 | --FILE-- 4 | struct A; 5 | struct B { 6 | struct A *a; 7 | struct A *b; 8 | }; 9 | 10 | --EXPECT-- 11 | struct A; 12 | struct B { 13 | struct A *a; 14 | struct A *b; 15 | }; -------------------------------------------------------------------------------- /test/cases/c/typedef.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of vars 3 | --FILE-- 4 | typedef void* foo; 5 | typedef unsigned char bar[23]; 6 | typedef long baz[][]; 7 | typedef enum qux { 8 | QUUX, 9 | CORGE, 10 | } grault; 11 | typedef char *(*(waldos))[5]; 12 | typedef char *(*(**hairy[][8])())[]; 13 | --EXPECT-- 14 | typedef void *foo; 15 | typedef unsigned char bar[23]; 16 | typedef long baz[][]; 17 | typedef enum qux { 18 | QUUX, 19 | CORGE, 20 | } grault; 21 | typedef char *(*(waldos))[5]; 22 | typedef char *(*(**hairy[][8])())[]; -------------------------------------------------------------------------------- /test/cases/c/vars.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of vars 3 | --FILE-- 4 | 5 | extern int foo; 6 | int bar; 7 | int baz[][]; 8 | char** qux; 9 | int* quux, corge; 10 | 11 | --EXPECT-- 12 | 13 | extern int foo; 14 | int bar; 15 | int baz[][]; 16 | char **qux; 17 | int *quux; 18 | int corge; -------------------------------------------------------------------------------- /test/cases/dump/bare_function_typedef.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test parsing of bare function typedefs being resolved to function decls 3 | --FILE-- 4 | 5 | typedef void *(func_def)(int *arg); 6 | typedef func_def func_alias; 7 | static func_alias foo; 8 | 9 | --EXPECT-- 10 | 11 | TranslationUnitDecl 12 | declarations: [ 13 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 14 | name: "func_def" 15 | type: Type_ParenType 16 | parent: Type_FunctionType_FunctionProtoType 17 | return: Type_PointerType 18 | parent: Type_BuiltinType 19 | name: "void" 20 | params: [ 21 | Type_PointerType 22 | parent: Type_BuiltinType 23 | name: "int" 24 | ] 25 | paramNames: [ 26 | arg 27 | ] 28 | isVariadic: false 29 | attributeList: [ 30 | ] 31 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 32 | name: "func_alias" 33 | type: Type_TypedefType 34 | name: "func_def" 35 | Decl_NamedDecl_ValueDecl_DeclaratorDecl_FunctionDecl 36 | name: "foo" 37 | type: Type_ExplicitAttributedType 38 | parent: Type_TypedefType 39 | name: "func_alias" 40 | kind: 2 41 | stmts: null 42 | declaratorAsm: null 43 | ] 44 | -------------------------------------------------------------------------------- /test/cases/dump/includes_and_typedefs.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic includes and typedefs 3 | --FILE-- 4 | #include "includes_and_typedefs.h" 5 | 6 | #ifdef TEST_FLAG 7 | typedef int A; 8 | #else 9 | typedef int B; 10 | #endif 11 | 12 | #ifdef TEST_FLAG2 13 | typedef int C; 14 | #else 15 | typedef int D; 16 | #endif 17 | 18 | --EXPECT-- 19 | TranslationUnitDecl 20 | declarations: [ 21 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 22 | name: "TEST" 23 | type: Type_BuiltinType 24 | name: "int" 25 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 26 | name: "A" 27 | type: Type_BuiltinType 28 | name: "int" 29 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 30 | name: "D" 31 | type: Type_BuiltinType 32 | name: "int" 33 | ] -------------------------------------------------------------------------------- /test/cases/dump/struct.phpt: -------------------------------------------------------------------------------- 1 | --TEST-- 2 | Test basic parsing of structs 3 | --FILE-- 4 | struct foo { 5 | int x, y; 6 | float z; 7 | }; 8 | 9 | typedef int foo; 10 | 11 | typedef struct foo bar; 12 | --EXPECT-- 13 | TranslationUnitDecl 14 | declarations: [ 15 | Decl_NamedDecl_TypeDecl_TagDecl_RecordDecl 16 | kind: 1 17 | name: "foo" 18 | fields: [ 19 | Decl_NamedDecl_ValueDecl_DeclaratorDecl_FieldDecl 20 | name: "x" 21 | type: Type_BuiltinType 22 | name: "int" 23 | bitfieldSize: null 24 | Decl_NamedDecl_ValueDecl_DeclaratorDecl_FieldDecl 25 | name: "y" 26 | type: Type_BuiltinType 27 | name: "int" 28 | bitfieldSize: null 29 | Decl_NamedDecl_ValueDecl_DeclaratorDecl_FieldDecl 30 | name: "z" 31 | type: Type_BuiltinType 32 | name: "float" 33 | bitfieldSize: null 34 | ] 35 | attributeList: [ 36 | ] 37 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 38 | name: "foo" 39 | type: Type_BuiltinType 40 | name: "int" 41 | Decl_NamedDecl_TypeDecl_TypedefNameDecl_TypedefDecl 42 | name: "bar" 43 | type: Type_TagType_RecordType 44 | decl: Decl_NamedDecl_TypeDecl_TagDecl_RecordDecl 45 | kind: 1 46 | name: "foo" 47 | fields: null 48 | attributeList: [ 49 | ] 50 | ] -------------------------------------------------------------------------------- /test/generated/c/basic_mathTest.c: -------------------------------------------------------------------------------- 1 | 2 | static long add2(long a, long b) { 3 | return a + b; 4 | } 5 | 6 | static long add_and_sub(long a, long b) { 7 | return (a + b) + 2 - 1 - b; 8 | } 9 | 10 | static long mul_sub(long a, long b) { 11 | return a * b - 3; 12 | } 13 | 14 | -------------------------------------------------------------------------------- /test/generated/c/basic_mathTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 31 | $this->parser->addSearchPath(__DIR__); 32 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 33 | $this->printer = new C; 34 | } 35 | 36 | /** 37 | * @textdox Test basic math operations 38 | */ 39 | public function testCode() { 40 | $translationUnit = $this->parser->parse(__DIR__ . '/basic_mathTest.c'); 41 | $actual = $this->printer->print($translationUnit); 42 | $this->assertEquals(self::EXPECTED, trim($actual)); 43 | } 44 | } -------------------------------------------------------------------------------- /test/generated/c/elifchainTest.c: -------------------------------------------------------------------------------- 1 | 2 | #if 0 3 | #elif 1 4 | #elif 0 5 | #elif 0 6 | #else 7 | #error "ERROR" 8 | #endif 9 | 10 | int bar; 11 | 12 | -------------------------------------------------------------------------------- /test/generated/c/elifchainTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 21 | $this->parser->addSearchPath(__DIR__); 22 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 23 | $this->printer = new C; 24 | } 25 | 26 | /** 27 | * @textdox Test for #elif chains 28 | */ 29 | public function testCode() { 30 | $translationUnit = $this->parser->parse(__DIR__ . '/elifchainTest.c'); 31 | $actual = $this->printer->print($translationUnit); 32 | $this->assertEquals(self::EXPECTED, trim($actual)); 33 | } 34 | } -------------------------------------------------------------------------------- /test/generated/c/escape_sequencesTest.c: -------------------------------------------------------------------------------- 1 | 2 | char a[] = { 3 | '\0', 4 | '\n', 5 | '\"', 6 | '\\', 7 | '\"', 8 | '\x12', 9 | '\377', 10 | }; 11 | const char *str = "a'b\n\"\\\"\x12\377\1"; 12 | 13 | -------------------------------------------------------------------------------- /test/generated/c/escape_sequencesTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 22 | $this->parser->addSearchPath(__DIR__); 23 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 24 | $this->printer = new C; 25 | } 26 | 27 | /** 28 | * @textdox Test escape sequence printing 29 | */ 30 | public function testCode() { 31 | $translationUnit = $this->parser->parse(__DIR__ . '/escape_sequencesTest.c'); 32 | $actual = $this->printer->print($translationUnit); 33 | $this->assertEquals(self::EXPECTED, trim($actual)); 34 | } 35 | } -------------------------------------------------------------------------------- /test/generated/c/expr1Test.c: -------------------------------------------------------------------------------- 1 | 2 | #define _FORTIFY_SOURCE 1 3 | #define __OPTIMIZE__ 2 4 | #define __GNUC_PREREQ(maj, min) ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) 5 | 6 | #if defined _FORTIFY_SOURCE && _FORTIFY_SOURCE > 0 \ 7 | && __GNUC_PREREQ (4, 1) && defined __OPTIMIZE__ && __OPTIMIZE__ > 0 8 | int success; 9 | #endif 10 | 11 | -------------------------------------------------------------------------------- /test/generated/c/expr1Test.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 21 | $this->parser->addSearchPath(__DIR__); 22 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 23 | $this->printer = new C; 24 | } 25 | 26 | /** 27 | * @textdox Test for bitshift 28 | */ 29 | public function testCode() { 30 | $translationUnit = $this->parser->parse(__DIR__ . '/expr1Test.c'); 31 | $actual = $this->printer->print($translationUnit); 32 | $this->assertEquals(self::EXPECTED, trim($actual)); 33 | } 34 | } -------------------------------------------------------------------------------- /test/generated/c/function_pointersTest.c: -------------------------------------------------------------------------------- 1 | typedef void *(*test_func)(int arg1_name, char *arg2_name); 2 | 3 | typedef void A(void * b); 4 | typedef void *(*with_void)(void); 5 | -------------------------------------------------------------------------------- /test/generated/c/function_pointersTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 23 | $this->parser->addSearchPath(__DIR__); 24 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 25 | $this->printer = new C; 26 | } 27 | 28 | /** 29 | * @textdox Test basic parsing of function pointers 30 | */ 31 | public function testCode() { 32 | $translationUnit = $this->parser->parse(__DIR__ . '/function_pointersTest.c'); 33 | $actual = $this->printer->print($translationUnit); 34 | $this->assertEquals(self::EXPECTED, trim($actual)); 35 | } 36 | } -------------------------------------------------------------------------------- /test/generated/c/functionsTest.c: -------------------------------------------------------------------------------- 1 | 2 | extern char** bar(char* arg1, int* arg2); 3 | static void foobar(void); 4 | 5 | -------------------------------------------------------------------------------- /test/generated/c/functionsTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 22 | $this->parser->addSearchPath(__DIR__); 23 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 24 | $this->printer = new C; 25 | } 26 | 27 | /** 28 | * @textdox Test basic parsing of functions 29 | */ 30 | public function testCode() { 31 | $translationUnit = $this->parser->parse(__DIR__ . '/functionsTest.c'); 32 | $actual = $this->printer->print($translationUnit); 33 | $this->assertEquals(self::EXPECTED, trim($actual)); 34 | } 35 | } -------------------------------------------------------------------------------- /test/generated/c/issue_2Test.c: -------------------------------------------------------------------------------- 1 | 2 | #ifndef TEST 3 | #define TEST 1 4 | #endif 5 | 6 | #if TEST 7 | int bar; 8 | #endif 9 | 10 | -------------------------------------------------------------------------------- /test/generated/c/issue_2Test.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 21 | $this->parser->addSearchPath(__DIR__); 22 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 23 | $this->printer = new C; 24 | } 25 | 26 | /** 27 | * @textdox Test for issue #2 28 | */ 29 | public function testCode() { 30 | $translationUnit = $this->parser->parse(__DIR__ . '/issue_2Test.c'); 31 | $actual = $this->printer->print($translationUnit); 32 | $this->assertEquals(self::EXPECTED, trim($actual)); 33 | } 34 | } -------------------------------------------------------------------------------- /test/generated/c/pragma_onceTest.c: -------------------------------------------------------------------------------- 1 | 2 | #include "pragma_once.h" 3 | #include "pragma_once.h" 4 | 5 | int TEST; 6 | 7 | -------------------------------------------------------------------------------- /test/generated/c/pragma_onceTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 21 | $this->parser->addSearchPath(__DIR__); 22 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 23 | $this->printer = new C; 24 | } 25 | 26 | /** 27 | * @textdox Test for #elif chains 28 | */ 29 | public function testCode() { 30 | $translationUnit = $this->parser->parse(__DIR__ . '/pragma_onceTest.c'); 31 | $actual = $this->printer->print($translationUnit); 32 | $this->assertEquals(self::EXPECTED, trim($actual)); 33 | } 34 | } -------------------------------------------------------------------------------- /test/generated/c/preprocessor_concatTest.c: -------------------------------------------------------------------------------- 1 | 2 | typedef char int8; 3 | 4 | #define DEF(a, b, c, d) a##b ba ## c ## d 5 | DEF(int, 8, 1, r;) 6 | 7 | -------------------------------------------------------------------------------- /test/generated/c/preprocessor_concatTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 22 | $this->parser->addSearchPath(__DIR__); 23 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 24 | $this->printer = new C; 25 | } 26 | 27 | /** 28 | * @textdox Test for preprocessor##concat 29 | */ 30 | public function testCode() { 31 | $translationUnit = $this->parser->parse(__DIR__ . '/preprocessor_concatTest.c'); 32 | $actual = $this->printer->print($translationUnit); 33 | $this->assertEquals(self::EXPECTED, trim($actual)); 34 | } 35 | } -------------------------------------------------------------------------------- /test/generated/c/preprocessor_ternaryTest.c: -------------------------------------------------------------------------------- 1 | 2 | #define A 1 3 | #define B 2 4 | #define C 3 5 | 6 | #if (A ? B : C) < 3 7 | #else 8 | #error "ERROR - 1" 9 | #endif 10 | 11 | #if !A ? B : C < 3 12 | #error "ERROR - 2" 13 | #endif 14 | 15 | #if !A ? B > 1 : !defined C 16 | #error "ERROR - 3" 17 | #endif 18 | 19 | #if A ? B > 1 ? C > 2 : A > 3 : defined C 20 | #error "ERROR - 4" 21 | #endif 22 | 23 | int bar; 24 | 25 | -------------------------------------------------------------------------------- /test/generated/c/preprocessor_ternaryTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 21 | $this->parser->addSearchPath(__DIR__); 22 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 23 | $this->printer = new C; 24 | } 25 | 26 | /** 27 | * @textdox Test for #if A ? B : C 28 | */ 29 | public function testCode() { 30 | $translationUnit = $this->parser->parse(__DIR__ . '/preprocessor_ternaryTest.c'); 31 | $actual = $this->printer->print($translationUnit); 32 | $this->assertEquals(self::EXPECTED, trim($actual)); 33 | } 34 | } -------------------------------------------------------------------------------- /test/generated/c/structTest.c: -------------------------------------------------------------------------------- 1 | struct foo { 2 | int x, y:2, :6; 3 | float z; 4 | }; 5 | 6 | typedef int foo; 7 | 8 | typedef struct foo bar; 9 | -------------------------------------------------------------------------------- /test/generated/c/structTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 28 | $this->parser->addSearchPath(__DIR__); 29 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 30 | $this->printer = new C; 31 | } 32 | 33 | /** 34 | * @textdox Test basic parsing of structs 35 | */ 36 | public function testCode() { 37 | $translationUnit = $this->parser->parse(__DIR__ . '/structTest.c'); 38 | $actual = $this->printer->print($translationUnit); 39 | $this->assertEquals(self::EXPECTED, trim($actual)); 40 | } 41 | } -------------------------------------------------------------------------------- /test/generated/c/struct_pointersTest.c: -------------------------------------------------------------------------------- 1 | struct A; 2 | struct B { 3 | struct A *a; 4 | struct A *b; 5 | }; 6 | 7 | -------------------------------------------------------------------------------- /test/generated/c/struct_pointersTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 25 | $this->parser->addSearchPath(__DIR__); 26 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 27 | $this->printer = new C; 28 | } 29 | 30 | /** 31 | * @textdox Test basic parsing of struct pointers 32 | */ 33 | public function testCode() { 34 | $translationUnit = $this->parser->parse(__DIR__ . '/struct_pointersTest.c'); 35 | $actual = $this->printer->print($translationUnit); 36 | $this->assertEquals(self::EXPECTED, trim($actual)); 37 | } 38 | } -------------------------------------------------------------------------------- /test/generated/c/typedefTest.c: -------------------------------------------------------------------------------- 1 | typedef void* foo; 2 | typedef unsigned char bar[23]; 3 | typedef long baz[][]; 4 | typedef enum qux { 5 | QUUX, 6 | CORGE, 7 | } grault; 8 | typedef char *(*(waldos))[5]; 9 | typedef char *(*(**hairy[][8])())[]; 10 | -------------------------------------------------------------------------------- /test/generated/c/typedefTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 29 | $this->parser->addSearchPath(__DIR__); 30 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 31 | $this->printer = new C; 32 | } 33 | 34 | /** 35 | * @textdox Test basic parsing of vars 36 | */ 37 | public function testCode() { 38 | $translationUnit = $this->parser->parse(__DIR__ . '/typedefTest.c'); 39 | $actual = $this->printer->print($translationUnit); 40 | $this->assertEquals(self::EXPECTED, trim($actual)); 41 | } 42 | } -------------------------------------------------------------------------------- /test/generated/c/varsTest.c: -------------------------------------------------------------------------------- 1 | 2 | extern int foo; 3 | int bar; 4 | int baz[][]; 5 | char** qux; 6 | int* quux, corge; 7 | 8 | -------------------------------------------------------------------------------- /test/generated/c/varsTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 26 | $this->parser->addSearchPath(__DIR__); 27 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 28 | $this->printer = new C; 29 | } 30 | 31 | /** 32 | * @textdox Test basic parsing of vars 33 | */ 34 | public function testCode() { 35 | $translationUnit = $this->parser->parse(__DIR__ . '/varsTest.c'); 36 | $actual = $this->printer->print($translationUnit); 37 | $this->assertEquals(self::EXPECTED, trim($actual)); 38 | } 39 | } -------------------------------------------------------------------------------- /test/generated/dump/bare_function_typedefTest.c: -------------------------------------------------------------------------------- 1 | 2 | typedef void *(func_def)(int *arg); 3 | typedef func_def func_alias; 4 | static func_alias foo; 5 | 6 | -------------------------------------------------------------------------------- /test/generated/dump/bare_function_typedefTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 53 | $this->parser->addSearchPath(__DIR__); 54 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 55 | $this->printer = new Dumper; 56 | } 57 | 58 | /** 59 | * @textdox Test parsing of bare function typedefs being resolved to function decls 60 | */ 61 | public function testCode() { 62 | $translationUnit = $this->parser->parse(__DIR__ . '/bare_function_typedefTest.c'); 63 | $actual = $this->printer->print($translationUnit); 64 | $this->assertEquals(self::EXPECTED, trim($actual)); 65 | } 66 | } -------------------------------------------------------------------------------- /test/generated/dump/includes_and_typedefsTest.c: -------------------------------------------------------------------------------- 1 | #include "includes_and_typedefs.h" 2 | 3 | #ifdef TEST_FLAG 4 | typedef int A; 5 | #else 6 | typedef int B; 7 | #endif 8 | 9 | #ifdef TEST_FLAG2 10 | typedef int C; 11 | #else 12 | typedef int D; 13 | #endif 14 | 15 | -------------------------------------------------------------------------------- /test/generated/dump/includes_and_typedefsTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 35 | $this->parser->addSearchPath(__DIR__); 36 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 37 | $this->printer = new Dumper; 38 | } 39 | 40 | /** 41 | * @textdox Test basic includes and typedefs 42 | */ 43 | public function testCode() { 44 | $translationUnit = $this->parser->parse(__DIR__ . '/includes_and_typedefsTest.c'); 45 | $actual = $this->printer->print($translationUnit); 46 | $this->assertEquals(self::EXPECTED, trim($actual)); 47 | } 48 | } -------------------------------------------------------------------------------- /test/generated/dump/structTest.c: -------------------------------------------------------------------------------- 1 | struct foo { 2 | int x, y; 3 | float z; 4 | }; 5 | 6 | typedef int foo; 7 | 8 | typedef struct foo bar; 9 | -------------------------------------------------------------------------------- /test/generated/dump/structTest.php: -------------------------------------------------------------------------------- 1 | parser = new CParser; 58 | $this->parser->addSearchPath(__DIR__); 59 | $this->parser->addSearchPath(__DIR__ . '/../../include'); 60 | $this->printer = new Dumper; 61 | } 62 | 63 | /** 64 | * @textdox Test basic parsing of structs 65 | */ 66 | public function testCode() { 67 | $translationUnit = $this->parser->parse(__DIR__ . '/structTest.c'); 68 | $actual = $this->printer->print($translationUnit); 69 | $this->assertEquals(self::EXPECTED, trim($actual)); 70 | } 71 | } -------------------------------------------------------------------------------- /test/include/includes_and_typedefs.h: -------------------------------------------------------------------------------- 1 | 2 | #define TEST_FLAG 3 | 4 | typedef int TEST; -------------------------------------------------------------------------------- /test/include/pragma_once.h: -------------------------------------------------------------------------------- 1 | 2 | #ifdef TEST 3 | #error "VERY BAD" 4 | #endif 5 | 6 | #pragma once 7 | 8 | #define TEST bar 9 | -------------------------------------------------------------------------------- /test/rebuild.php: -------------------------------------------------------------------------------- 1 | assertEquals(self::EXPECTED, trim($actual));'; 91 | } else { 92 | throw new \LogicException("Unknown test expectation type"); 93 | } 94 | 95 | $return = 'parser = new CParser; 115 | $this->parser->addSearchPath(__DIR__); 116 | $this->parser->addSearchPath(__DIR__ . ' . var_export($searchPath, true) . '); 117 | $this->printer = new ' . ($isDump ? 'Dumper' : 'C') . '; 118 | } 119 | 120 | /** 121 | * @textdox ' . $test[1] . ' 122 | */ 123 | public function testCode() { 124 | $translationUnit = $this->parser->parse(__DIR__ . ' . var_export('/' . $relativeTarget . '.c', true) . '); 125 | $actual = $this->printer->print($translationUnit); 126 | ' . $assert . ' 127 | } 128 | }'; 129 | file_put_contents($targetFile . '.php', $return); 130 | 131 | } 132 | 133 | 134 | 135 | 136 | 137 | function provideTestsFromDir(string $dir): \Generator { 138 | foreach (new \DirectoryIterator($dir) as $path) { 139 | if (!$path->isDir() || $path->isDot()) { 140 | continue; 141 | } 142 | yield from provideTestsFromDir($path->getPathname()); 143 | } 144 | foreach (new \GlobIterator($dir . '/*.phpt') as $test) { 145 | yield parseTest($test->getPathname()); 146 | } 147 | } 148 | 149 | function parseTest(string $filename): array { 150 | $sections = []; 151 | $section = ''; 152 | foreach (file($filename) as $line) { 153 | if (preg_match('(^--([_A-Z]+)--)', $line, $result)) { 154 | $section = $result[1]; 155 | $sections[$section] = ''; 156 | continue; 157 | } 158 | if (empty($section)) { 159 | throw new \LogicException("Invalid PHPT file: empty section header"); 160 | } 161 | $sections[$section] .= $line; 162 | } 163 | if (!isset($sections['TEST'])) { 164 | throw new \LogicException("Every test must have a name"); 165 | } 166 | if (isset($sections['FILEEOF'])) { 167 | $sections['FILE'] = rtrim($sections['FILEEOF'], "\r\n"); 168 | unset($sections['FILEEOF']); 169 | } 170 | parseExternal($sections, dirname($filename)); 171 | if (!validate($sections)) { 172 | throw new \LogicException("Invalid PHPT File"); 173 | } 174 | foreach (UNSUPPORTED_SECTIONS as $section) { 175 | if (isset($sections[$section])) { 176 | throw new \LogicException("PHPT $section sections are not supported"); 177 | } 178 | } 179 | return [ 180 | $filename, 181 | trim($sections["TEST"]), 182 | $sections['FILE'], 183 | $sections, 184 | ]; 185 | } 186 | 187 | function parseExternal(array &$sections, string $testdir): void { 188 | foreach (EXTERNAL_SECTIONS as $section) { 189 | if (isset($sections[$section . '_EXTERNAL'])) { 190 | $filename = trim($sections[$section . '_EXTERNAL']); 191 | if (!is_file($testdir . '/' . $filename)) { 192 | throw new \RuntimeException("Could not load external file $filename"); 193 | } 194 | $sections[$section] = file_get_contents($testdir . '/' . $filename); 195 | } 196 | } 197 | } 198 | 199 | function validate(array &$sections): bool { 200 | foreach (REQUIRED_SECTIONS as $section) { 201 | if (is_array($section)) { 202 | foreach ($section as $any) { 203 | if (isset($sections[$any])) { 204 | continue 2; 205 | } 206 | } 207 | return false; 208 | } elseif (!isset($sections[$section])) { 209 | return false; 210 | } 211 | } 212 | return true; 213 | } 214 | --------------------------------------------------------------------------------