├── README.md ├── build_extensions.php ├── composer.json └── src └── PHPtoCExt ├── Converter.php ├── Converter ├── CFunctionAutoConverter.php ├── CFunctionCallConverter.php ├── ClassHierarchyFlatterningConverter.php ├── CodeReformatConverter.php ├── ForLoopToWhileLoopConverter.php ├── IssetToNotEmptyConverter.php ├── ModuloCastingConverter.php ├── PrintToEchoConverter.php ├── SelfStaticConverter.php ├── StaticVarAutoDefineConverter.php └── TraitMergingConverter.php ├── FileAnalyser.php ├── FileFilter.php └── PHPtoCExtException.php /README.md: -------------------------------------------------------------------------------- 1 | PHP-TO-C-Ext is a tool to allow developer to build Zend Engine based PHP Extensions using PHP together with C. 2 | 3 | PHP-TO-C-EXT is built on top of these great things: 4 | 5 | + Zephir (http://zephir-lang.com/) 6 | + PHP Parser (https://github.com/nikic/PHP-Parser) 7 | + PHP to Zephir (https://github.com/fezfez/php-to-zephir) 8 | 9 | PHP-TO-C-EXT is tested on Fedora Linux 21 and Mac OS Yosemite From PHP 5.4 to 5.6. 10 | 11 | ##Installation 12 | 13 | 1. Install composer 14 | 2. git clone https://github.com/jimthunderbird/php-to-c-extension.git 15 | 3. cd php-to-c-extension 16 | 4. composer.phar install 17 | 18 | ##Usage: 19 | 20 | ```sh 21 | $ php [path/to/php-to-c-extension]/build_extensions.php [php file to convert to c extension] 22 | ``` 23 | 24 | or 25 | 26 | ```sh 27 | $ php [path/to/php-to-c-extension]/build_extensions.php [directory containing php files to convert to c extension] 28 | ``` 29 | 30 | ##Examples: 31 | 32 | + [A simple dummy extension](#example-01) 33 | + [One namespace and multiple classes in one file](#example-02) 34 | + [Organize multiple files in one directory](#example-03) 35 | + [Using for loop](#example-04) 36 | + [Using sub-namespaces] (#example-05) 37 | + [Using interface] (#example-06) 38 | + [Using trait] (#example-07) 39 | + [Calling method in the base class with parent::](#example-08) 40 | + [Using the self keyword](#example-09) 41 | + [Using ternary operator](#example-10) 42 | + [Late static binding](#example-11) 43 | + [Performance Benchmark: Bubble Sort](#example-12) 44 | + [Gain greater speed through raw C code, using call_c_function](#example-13) 45 | + [Using PHP Code together with raw C code to solve problems, using call_c_function](#example-14) 46 | + [Better code structure and saving development time when working with raw C code, using call_c_auto](#example-15) 47 | 48 | ###Example 01 49 | 50 | Let's create a file named Dummy.php, it looks like this: 51 | 52 | ```php 53 | say(); 82 | ``` 83 | #### and if we run it with php -c [path/to/php.ini]/php.ini test.php, we should get "hello" printed. 84 | #### You might have already noticed, the class Hello has the namespace Dummy and the extension name is dummy.so. 85 | #### In fact, in order to build a php extension with this tool, all classes must have a CamelCase namespace, and the extension name is the lowercase form of the namespace. 86 | 87 | 88 | ###Example 02 89 | ####Sometimes, for convenience, we might want to write a single file with one namespace and multiple classes, and we can do just that. 90 | ####Let's create a file named Dummy.php an it looks like the following: 91 | ```php 92 | getSum(1,10); 176 | ``` 177 | 178 | ###Example 05 179 | ####We can use sub namespaces to better manage the cod in the extension. 180 | ####When using sub namespaces, we need to make sure the first part of the sub namespace match is name of our extension. 181 | ####In this example, let's create a dummy extension with the following files. 182 | ####1. src/Dummy/Vehicle.php 183 | ####2. src/Dummy/Vehicle/Car.php 184 | ####src/Dummy/Vehicle.php looks like this: 185 | ```php 186 | say(); 213 | ``` 214 | ####We will have "I am a vehicle" printed. 215 | 216 | ###Example 06 217 | ####We can use interface just like what we do in normal php code. 218 | ####Let's created the following files: 219 | ####1. src/Dummy/MovableInterface.php 220 | ####2. src/Dummy/Vehicle.php 221 | ####src/Dummy/MovableInterface.php looks like this: 222 | ```php 223 | move(); 250 | ``` 251 | ####We will have "I am moving" printed. 252 | 253 | ###Example 07 254 | ####Trait is a new feature introduced in php 5.4 to allow grouping of functionalities and reused by individual classes. 255 | ####In the example below, we are going to demonstrate using trait to build a dummy php extension 256 | ####We are going to create a file src/dummy.php, and it looks like this: 257 | ```php 258 | play(); 294 | $vehicle->move(); 295 | ``` 296 | ####We will see the "I am playing.I am moving." printed 297 | 298 | ###Example 08 299 | ####When we need to call certain methods in the base class, we will need to use the parent keyword, below is an example: 300 | ####Let's create a file named dummy.php and it looks like this: 301 | ```php 302 | stop(); 343 | ``` 344 | ####We should see the following printed 345 | ####"I am a new vehicle.I am also a new car.I am stopping now.don't worry i am a car." 346 | 347 | ###Example 09 348 | ####We can use the self keyword just like normal php to build a php extension, below is an example: 349 | ####Let's create a file named dummy.php and it looks like this: 350 | ```php 351 | stop()."\n"; 409 | ``` 410 | ####We should see the following printed 411 | ####"I am a new vehicle.Two cars are identical 412 | ####I am stopping now." 413 | 414 | ###Example 10 415 | ####We can use ternary operator as a shortcut to write conditional statemements and variable assignmens 416 | ####Let's create a file named dummy.php and it looks like this: 417 | ```php 418 | number = $number; 428 | } 429 | 430 | public function isPositive() 431 | { 432 | $result = ($this->number > 0)?true:false; 433 | return $result; 434 | } 435 | } 436 | ``` 437 | ####Then once we dummy.so built, in the user code if we do the following: 438 | ```php 439 | isPositive() === TRUE) { 442 | echo "This is a positive number."; 443 | } 444 | ``` 445 | ####We then should see the following printed on the screen. 446 | ####"This is a positive number." 447 | 448 | ###Example 11 449 | ####Late static binding is introduced in PHP 5.3 and used to reference the called class in a context of static inheritance. 450 | ####Below is an example of late static binding 451 | ####Let's create a file named src/dummy.php and it looks like this: 452 | ```php 453 | namespace Dummy; 454 | class Model 455 | { 456 | protected static $name = "model"; 457 | public static function find() 458 | { 459 | echo static::$name; 460 | } 461 | } 462 | 463 | class Product extends Model 464 | { 465 | protected static $name = 'Product'; 466 | } 467 | ``` 468 | ####Then once we have dummy.so built and if we do the following: 469 | ```php 470 | Dummy\Product::find(); 471 | ``` 472 | ####We should have "Product" printed on the screen. 473 | 474 | ###Example 12 475 | ####Below let's do a simple benchmark to see how fast the php extension will be 476 | ####We will be using bubble sort as an example to demonstrate the time difference. Here is our code for src/dummy.php: 477 | ```php 478 | = 1; $i--) { 521 | $arr[] = $i; 522 | } 523 | 524 | $time_start = microtime_float(); 525 | 526 | $st = new Dummy\Sorter(); 527 | $arr = $st->bubbleSort($arr); 528 | 529 | $time_end = microtime_float(); 530 | $time = $time_end - $time_start; 531 | 532 | print "Time spent on sorting: ".$time." seconds.\n"; 533 | ``` 534 | ####The code above is pretty straightforward, it first detect if we have the Dummy\Sorter class defined, if it is defined, that means the dummy.so extension is loaded, otherwise, we will just require the pure php version of Dummy\Sorter class. 535 | ####We then generate an array of 10000 integers and ask Dummy\Sorter to bubble sort it. 536 | ####This is also the beauty of having the ability to write our extension in php itself, since we can seamlessly compare the performance. 537 | ####Now if we just do: 538 | ```sh 539 | php test.php 540 | ``` 541 | ####We will be just using the pure php version, in my intel core i3 laptop with 4 core cpu running fedora 21 and PHP 5.6.4, it shows the following: 542 | ####Time spent on sorting: 16.802139997482 seconds. 543 | ####Now let's test the php extension see how it performs. We first create php.ini and then inside we have: 544 | ```sh 545 | extension=dummy.so 546 | ``` 547 | ####Then if we do php -c php.ini test.php, we will be using the dummy.so to do the bubble sort for us, in my laptop it shows the following: 548 | ####Time spent on sorting: 3.9628620147705 seconds. 549 | ####As you can see, the php extension dummy.so that we built is about 3 times faster than the pure php version. And we are seamlessly using the same Dummy\Sorter class! 550 | 551 | ###Example 13 552 | ####PHP is built on top of Zend Engine, which is written in C. It will be great that we could use C code inside PHP to gain higher performance. 553 | ####In this tool, we have a way to do so, by using the call_c_function api. 554 | ####In the example below, we will be using the call_c_function api to call the C based bubble sort implementation from within PHP. 555 | ####This example requires advance knowledge of the internal data structure of the Zend Engine. 556 | ####First, let's create src/dummy.php 557 | ```php 558 | value.ht; 581 | long arr_length = arr_hash->nNumOfElements; 582 | long i,j; 583 | 584 | zval **p; 585 | 586 | long sorting_arr[arr_length]; 587 | long tmp; 588 | 589 | for (i=0; i < arr_length; i++) { 590 | p = (zval **)(arr_hash->arBuckets[i]->pData); 591 | sorting_arr[i] = (*p)->value.lval; 592 | } 593 | 594 | //perform bubble sort 595 | for (i=0; i< arr_length; i++) { 596 | for (j=0; j= 1; $i--) { 640 | $arr[] = $i; 641 | } 642 | 643 | $time_start = microtime_float(); 644 | 645 | $st = new Dummy\Sorter(); 646 | $arr = $st->bubbleSort($arr); 647 | 648 | $time_end = microtime_float(); 649 | $time = $time_end - $time_start; 650 | 651 | print "Time spent on sorting: ".$time." seconds.\n"; 652 | ``` 653 | ####Now if we add extension=dummy.so to a php.ini file and do 654 | ```sh 655 | $ php -c php.ini test.php 656 | ``` 657 | ####We will see the following printed on the screen 658 | ####Time spent on sorting: 0.14397192001343 seconds. 659 | ####The result is really great. Compared to 3.9628620147705 seconds in example 12 and 16.802139997482 seconds for the pure PHP version, it is significantly faster. 660 | 661 | ###Example 14 662 | ####In this example below, we will be using PHP code together with raw C code to get the first 800 digits of PI. 663 | ####The algorithm to compute PI is borrowed from https://crypto.stanford.edu/pbc/notes/pi/code.html 664 | ####First, we will be creating the src/dummy.php and it looks like this: 665 | ```php 666 | 0; k -= 14) { 702 | d = 0; 703 | 704 | i = k; 705 | for (;;) { 706 | d += r[i] * 10000; 707 | b = 2 * i - 1; 708 | 709 | r[i] = d % b; 710 | d /= b; 711 | i--; 712 | if (i == 0) break; 713 | d *= i; 714 | } 715 | 716 | sprintf(tmp_buf, "%.4d", c + d / 10000); 717 | 718 | buf[count] = tmp_buf[0]; 719 | buf[count+1] = tmp_buf[1]; 720 | buf[count+2] = tmp_buf[2]; 721 | buf[count+3] = tmp_buf[3]; 722 | 723 | c = d % 10000; 724 | 725 | count += 4; 726 | } 727 | 728 | buf[count+1] = '\0'; 729 | 730 | ZVAL_STRING(result, buf, 1); 731 | 732 | return result; 733 | } 734 | ``` 735 | ####Then we will have test.php 736 | ```php 737 | getPI(); 740 | ``` 741 | ####Then once dummy.so is built and we do: 742 | ```sh 743 | $ php -c php.ini test.php 744 | ``` 745 | ####We will be seeing the first 800 digits of PI printed on the screen. 746 | 747 | ###Example 15 748 | ####In example 13 and 14 above, we demonstrate the ability to work with PHP and raw C code to solve problems. 749 | ####One thing that came up is, if we are developing on a large extension codebase, using call_c_function and specifying the C source file name and C function name is a bit time consuming and at some points might become tedious. This is when the API call_c_auto comes into help. 750 | ####Below we will demonstrate using call_c_auto to do bubble sort. 751 | ####Let's first create file src/Dummy/Sorter.php: 752 | ```php 753 | 0) { 762 | $result = call_c_auto($arr); 763 | } 764 | return $result; 765 | } 766 | } 767 | ``` 768 | ####Notice this line here: 769 | ```php 770 | $result = call_c_auto($arr); 771 | ``` 772 | ####What we are doing is to pass the $arr input param to the call_c_auto API. Under the hook the extension building process will convert this API to: 773 | ```php 774 | $result = call_c_function("Sorter.c","bubbleSort",$arr); 775 | ``` 776 | ####See the Sorter.c has the same name as the class name Sorter, and the C function we will be calling has the same name as the PHP method name bubbleSort. 777 | ####This convention helps us organize the code better and save some typing. 778 | ####Now let's create src/Dummy/Sorter.c 779 | ```php 780 | static zval * bubbleSort(zval * arr) 781 | { 782 | HashTable *arr_hash = arr->value.ht; 783 | long arr_length = arr_hash->nNumOfElements; 784 | long i,j; 785 | 786 | zval **p; 787 | 788 | long sorting_arr[arr_length]; 789 | long tmp; 790 | 791 | for (i=0; i < arr_length; i++) { 792 | p = (zval **)(arr_hash->arBuckets[i]->pData); 793 | sorting_arr[i] = (*p)->value.lval; 794 | } 795 | 796 | //perform bubble sort 797 | for (i=0; i< arr_length; i++) { 798 | for (j=0; jbubbleSort($arr); 827 | 828 | print_r($arr); 829 | ``` 830 | ####And let's build our extension by doing: 831 | ```sh 832 | $ php [path/to/php-to-c-extension]/build_extensions.php src/Dummy 833 | ``` 834 | ####Then once dummy.so is built and we add extension=dummy.so to php.ini, we can run our test code: 835 | ```sh 836 | $php -c php.ini test.php 837 | ``` 838 | ####And we will see that our array is nicely sorted. 839 | ####The advantage of using call_c_auto in this example is, we have src/Dummy/Sorter.php and src/Dummy/Sorter.c and they interact nicely to solve our problem. 840 | -------------------------------------------------------------------------------- /build_extensions.php: -------------------------------------------------------------------------------- 1 | ") { 69 | $fc = substr($fc, 0, strlen($fc) -2); 70 | } 71 | $fileContent .= $fc."\n\n"; 72 | } 73 | 74 | //trim file content again 75 | $fileContent = trim($fileContent); 76 | 77 | $file = $zephirDir."/".array_pop(explode("/",$input)).".php"; 78 | } 79 | 80 | file_put_contents($file, $fileContent); 81 | 82 | $targetFile = $zephirDir."/".basename($file); 83 | 84 | $fileFilter = null; 85 | 86 | try { 87 | 88 | $fileFilter = new PHPtoCExt\FileFilter($file, $targetFile, $inputDir); 89 | $fileFilter->filter(); 90 | 91 | $analyser = new PHPtoCExt\FileAnalyser($targetFile); 92 | 93 | foreach($analyser->getUserDefinedClasses() as $class) { 94 | $classCode = $analyser->getCodeInClass($class); 95 | $zephirNamespace = strtolower($analyser->getRootNamespaceOfClass($class)); 96 | if (chdir($zephirDir)) { 97 | system("$zephirCommand init $zephirNamespace"); 98 | $classFileDir = strtolower(str_replace("\\","/",$analyser->getNamespaceOfClass($class))); 99 | system("mkdir -p ".$zephirNamespace."/".$classFileDir); 100 | if (!is_readable($zephirNamespace."/".$classFileDir)) { 101 | throw new PHPtoCExt\PHPtoCExtException("Fail to create directory ".$classFileDir); 102 | } 103 | $classFileName = $zephirNamespace."/".$classFileDir."/".$analyser->getClassNameWithoutNamespace($class).".php"; 104 | file_put_contents($classFileName, "getMessage()."\n"); 113 | } 114 | 115 | foreach($extensionNames as $extensionName) { 116 | $zephirProjectDir = $zephirDir."/".$extensionName; 117 | if (chdir($zephirProjectDir) ) { 118 | system(__DIR__."/vendor/bin/php-to-zephir phpToZephir:convertDir $extensionName"); 119 | 120 | //now do post convertion searches and replaces 121 | echo "Performing post conversion processing...\n"; 122 | 123 | $convertedFiles = explode("\n",trim(shell_exec("find . -type f -name \"*.zep\""))); 124 | 125 | foreach($convertedFiles as $file) { 126 | $fileFilter->postFilter($file); 127 | } 128 | 129 | echo "Finished post conversion processing\n"; 130 | 131 | echo "Building extension...\n"; 132 | system("$zephirCommand install"); 133 | 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jimthunderbird/php-to-c-extension", 3 | "description": "A command line tool to build Zend Based PHP extensions using PHP and C", 4 | "keywords": ["tool", "zephir", "extension", "builder"], 5 | "homepage": "https://github.com/jimthunderbird/php-to-c-extension", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Zike Huang", 10 | "email": "jim.javathunderbird@gmail.com", 11 | "homepage": "http://www.pamground.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "minimum-stability" : "dev", 16 | "require": { 17 | "php": ">=5.3.0" 18 | }, 19 | "require-dev": { 20 | "fezfez/php-to-zephir":"dev-master#5ea8dc05d759b64fa2590580d57c6016e55de833" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "PHPtoCExt": "src" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter.php: -------------------------------------------------------------------------------- 1 | codeLines = $codeLines; 17 | $this->codeASTXMLLines = $codeASTXMLLines; 18 | $this->inputDir = $inputDir; 19 | $this->searches = array(); 20 | $this->replaces = array(); 21 | $this->postSearches = array(); 22 | $this->postReplaces = array(); 23 | } 24 | 25 | public function getSearches() 26 | { 27 | return $this->searches; 28 | } 29 | 30 | public function getReplaces() 31 | { 32 | return $this->replaces; 33 | } 34 | 35 | public function getPostSearches() 36 | { 37 | return $this->postSearches; 38 | } 39 | 40 | public function getPostReplaces() 41 | { 42 | return $this->postReplaces; 43 | } 44 | 45 | protected function searchAndReplace($search, $replace) 46 | { 47 | $this->searches[] = $search; 48 | $this->replaces[] = $replace; 49 | } 50 | 51 | protected function postSearchAndReplace($search, $replace) 52 | { 53 | $this->postSearches[] = $search; 54 | $this->postReplaces[] = $replace; 55 | } 56 | 57 | //some util methods for all converters to use, maybe refactor later on to a better place? 58 | public function getClassMap() 59 | { 60 | //get all classes info, with namespace 61 | $classInfos = array(); 62 | 63 | $classMap = array(); 64 | 65 | $namespace = ""; 66 | $className = ""; 67 | 68 | foreach($this->codeASTXMLLines as $index => $line) 69 | { 70 | if (strpos($line,"") > 0) { 71 | $startLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 2]); 72 | $namespace = str_replace(array("namespace ",";"),"",$this->codeLines[$startLine - 1]); 73 | } else if (strpos($line,"") > 0) { 74 | $classInfo = new \stdClass(); 75 | $classInfo->startLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 2]); 76 | //is it an abstract class? 77 | $classInfo->isAbstract = false; 78 | if (strpos(trim($this->codeLines[$classInfo->startLine-1]), "abstract ") === 0) { 79 | $classInfo->isAbstract = true; 80 | } 81 | $classInfo->endLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 5]); 82 | $classInfo->namespace = $namespace; 83 | $classInfo->className = "\\".$namespace."\\".trim(str_replace(array("",""),"",$this->codeASTXMLLines[$index + 11])); 84 | $classInfo->methodInfos = array(); 85 | $classInfo->properties = array(); 86 | $classInfo->staticProperties = array(); 87 | $className = $classInfo->className; 88 | 89 | $classInfos[] = $classInfo; 90 | 91 | $classMap[$classInfo->className] = $classInfo; 92 | } else if (strpos($line, "") > 0) { 93 | $propertyStartLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 2]); 94 | $propertyCode = $this->codeLines[$propertyStartLine - 1]; 95 | $propertyInfo = new \stdClass(); 96 | 97 | $propertyStartPos = strpos($propertyCode, "$"); 98 | $propertyEndPos = strpos($propertyCode, ";"); 99 | 100 | $propertyComps = explode("=",substr($propertyCode, $propertyStartPos + 1, $propertyEndPos - $propertyStartPos - 1)); 101 | 102 | $propertyInfo->name = trim($propertyComps[0]); 103 | $propertyInfo->value = null; 104 | //some property might not have a value; 105 | if (count($propertyComps) > 1) { 106 | $propertyInfo->value = str_replace('"',"'", trim($propertyComps[1])); 107 | } 108 | $propertyInfo->code = $propertyCode; 109 | 110 | if(strpos($propertyCode, "static ") !== FALSE) { 111 | //this is a static property 112 | $classMap[$className]->staticProperties[] = $propertyInfo; 113 | } else { 114 | //this is a dynamic property 115 | $classMap[$className]->properties[] = $propertyInfo; 116 | } 117 | } else if (strpos($line,"") > 0) { 118 | $classMethodInfo = new \stdClass(); 119 | $classMethodInfo->startLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 2]); 120 | $classMethodInfo->endLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 5]); 121 | $startLineContent = $this->codeLines[$classMethodInfo->startLine - 1]; 122 | $classMethodInfo->name = trim(explode("function ",$startLineContent)[1]); 123 | $classMethodInfo->pureName = explode(" ", str_replace("(", " ", $classMethodInfo->name))[0]; 124 | //now figure out where it is public, protected or private 125 | 126 | //find out all methods belongs to this class 127 | foreach(array("public","protected","private") as $visibility) { 128 | if (strpos($startLineContent,"$visibility ") !== FALSE) { 129 | $classMethodInfo->visibility = $visibility; 130 | } 131 | } 132 | 133 | if (!isset($classMethodInfo->visibility)) { 134 | $classMethodInfo->visibility = "protected"; 135 | } 136 | 137 | if (strpos($startLineContent, "static ") !== FALSE) { 138 | $classMethodInfo->isStatic = true; 139 | } else { 140 | $classMethodInfo->isStatic = false; 141 | } 142 | 143 | $classMap[$className]->methodInfos[$classMethodInfo->pureName] = $classMethodInfo; 144 | } 145 | } 146 | 147 | //now figure out the parent classes for each class 148 | foreach($classInfos as $index => $classInfo) { 149 | $line = trim($this->codeLines[$classInfo->startLine - 1]); 150 | if (strpos($line, " extends ") !== FALSE) { 151 | $lineComps = explode(" extends ", $line); 152 | $classMap[$classInfo->className]->parentClass = "\\".$classInfo->namespace."\\".trim(explode(" ",$lineComps[1])[0]); 153 | } 154 | } 155 | 156 | return $classMap; 157 | } 158 | 159 | abstract public function convert(); 160 | } 161 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/CFunctionAutoConverter.php: -------------------------------------------------------------------------------- 1 | getClassMap(); 13 | 14 | foreach($classMap as $className => $classInfo) { 15 | $originalClassCode = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 16 | foreach($classInfo->methodInfos as $methodPureName => $methodInfo) { 17 | for ($index = $methodInfo->startLine; $index <= $methodInfo->endLine; $index++) { 18 | if (preg_match("/call_c_auto\(.*\)/", $this->codeLines[$index], $matches)) { 19 | if(count($matches) == 1) { 20 | $codeLine = $this->codeLines[$index]; //store the original code line here 21 | $filteredCodeLine = str_replace(array("call_c_auto","(",")",";"),"",trim($codeLine)); 22 | $lineComps = explode("=",$filteredCodeLine); 23 | $lineCompsCount = count($lineComps); 24 | $className[0] = ""; 25 | $className = trim($className); 26 | $classNameComps = explode("\\", $className); 27 | //remove namespace 28 | array_shift($classNameComps); 29 | $className = implode("/",$classNameComps); 30 | if ($lineCompsCount == 1) { //this means we do not have return result variable 31 | $inputParamsStr = trim($lineComps[0]); 32 | if (strlen($inputParamsStr) == 0) { 33 | $this->searchAndReplace($filteredCodeLine."call_c_auto();","call_c_function(\"$className.c\",\"$methodPureName\");"); 34 | } else { 35 | $this->searchAndReplace($codeLine,"call_c_function(\"$className.c\",\"$methodPureName\",$inputParamsStr);"); 36 | } 37 | } else if ($lineCompsCount == 2){ //this means we have input return result variable 38 | $returnVarStr = $lineComps[0]; 39 | $inputParamsStr = trim($lineComps[1]); 40 | if (strlen($inputParamsStr) == 0) { 41 | $this->searchAndReplace($filteredCodeLine."call_c_auto();","$returnVarStr = call_c_function(\"$className.c\",\"$methodPureName\");"); 42 | } else { 43 | $this->searchAndReplace($codeLine,"$returnVarStr = call_c_function(\"$className.c\",\"$methodPureName\",$inputParamsStr);"); 44 | } 45 | 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/CFunctionCallConverter.php: -------------------------------------------------------------------------------- 1 | getClassMap(); 15 | 16 | $cSourceCodeMap = array(); 17 | 18 | foreach($classMap as $className => $classInfo) { 19 | $originalClassCode = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 20 | foreach($classInfo->methodInfos as $methodPureName => $methodInfo) { 21 | $tmpCFuncCallResultDefined = false; 22 | for ($index = $methodInfo->startLine; $index <= $methodInfo->endLine; $index++) { 23 | if (preg_match("/call_c_function\(.*\)/", $this->codeLines[$index], $matches)) { 24 | 25 | if(count($matches) == 1) { 26 | //we simply add $tmpCFuncCallResult = null;\n at the beginning of the method 27 | $codeLine = $this->codeLines[$index]; //store the original code line here 28 | if (!$tmpCFuncCallResultDefined) { 29 | $this->codeLines[$index] = "\$tmpCFuncCallResult = null;\n".$this->codeLines[$index]; 30 | $tmpCFuncCallResultDefined = true; 31 | } 32 | 33 | $codeLine = str_replace(array("call_c_function","(",")",";","$"),"",trim($codeLine)); 34 | $lineComps = explode("=",$codeLine); 35 | $lineCompsCount = count($lineComps); 36 | $resultVarName = ""; 37 | if ($lineCompsCount == 1) { //this means we just call the c function and do not have a return variable 38 | $cFunctionCallComps = explode(",",$lineComps[0]); 39 | foreach($cFunctionCallComps as $idx => $comp) { 40 | $cFunctionCallComps[$idx] = trim(str_replace("$","",$cFunctionCallComps[$idx])); 41 | } 42 | } else if ($lineCompsCount > 1) { //this means we call the c function and then store the result in a return variable 43 | $resultVarName = trim(str_replace("$","",$lineComps[0])); 44 | $cFunctionCallComps = explode(",",$lineComps[1]); 45 | foreach($cFunctionCallComps as $idx => $comp) { 46 | $cFunctionCallComps[$idx] = trim(str_replace("$","",$cFunctionCallComps[$idx])); 47 | } 48 | } 49 | 50 | if (count($cFunctionCallComps) > 0) { 51 | $firstComp = array_shift($cFunctionCallComps); 52 | $cSourceFile = str_replace(array("'",'"'),"",$firstComp); 53 | $secondComp = array_shift($cFunctionCallComps); 54 | //get the called c function name 55 | $cFUnctionName = str_replace(array("'",'"'),"",$secondComp); 56 | //prepend c file name on each called c function 57 | //we need to consider that the c file name might have / in it, and we need to change that to _ 58 | $cSourceFilePureName = str_replace("/","_",$cSourceFile); 59 | $cFUnctionName = explode(".",$cSourceFilePureName)[0]."_".$cFUnctionName; 60 | $cFUnctionInputParamsStr = ""; 61 | if (count($cFunctionCallComps) > 0) { 62 | $cFUnctionInputParamsStr = implode(", ",$cFunctionCallComps); 63 | } 64 | 65 | $cFunctionCallCode = ""; 66 | if (strlen($resultVarName) == 0) { 67 | $cFunctionCallCode .= "\n%{\n"; 68 | $cFunctionCallCode .= $cFUnctionName."($cFUnctionInputParamsStr);"; 69 | $cFunctionCallCode .= "\n}%\n"; 70 | if (strlen($cFUnctionInputParamsStr) == 0) { 71 | $expectedZephirCode = 'call_c_function('.$firstComp.', '.$secondComp.');'; 72 | } else { 73 | $expectedZephirCode = 'call_c_function('.$firstComp.', '.$secondComp.', '.implode(", ",$cFunctionCallComps).');'; 74 | } 75 | } else { 76 | $cFunctionCallCode .= "let $resultVarName = null;\n"; //initialize result var 77 | $cFunctionCallCode .= "\n%{\n"; 78 | $cFunctionCallCode .= "tmpCFuncCallResult = ".$cFUnctionName."($cFUnctionInputParamsStr);\n"; 79 | $cFunctionCallCode .= "\n}%\n"; 80 | $cFunctionCallCode .= "let $resultVarName = tmpCFuncCallResult;\n"; 81 | if (strlen($cFUnctionInputParamsStr) == 0) { 82 | $expectedZephirCode = 'let '.$resultVarName.' = call_c_function('.$firstComp.', '.$secondComp.');'; 83 | } else { 84 | $expectedZephirCode = 'let '.$resultVarName.' = call_c_function('.$firstComp.', '.$secondComp.', '.implode(", ",$cFunctionCallComps).');'; 85 | } 86 | } 87 | 88 | $this->postSearchAndReplace($expectedZephirCode,$cFunctionCallCode); 89 | 90 | //now, inject the c source code to the top of the class 91 | $namespace = $classInfo->namespace; 92 | $classPureName = array_pop(explode("\\",$className)); 93 | $originalCode = "namespace $namespace;\n\n"."class $classPureName\n"; 94 | //make sure we only have one unique copy of c source file per class 95 | $classNameCSourceFileKey = $className.".".$cSourceFile; 96 | if (!isset($cSourceCodeMap[$classNameCSourceFileKey])) { 97 | //read the c source file content 98 | $cSourceCode = file_get_contents($this->inputDir."/".$cSourceFile); 99 | 100 | //now prefix the c source file's pure name to each function and corresponding function calls 101 | //this way, we will be sure that we will not have the same function name defined in more than one c source file 102 | $cSourceCode = $this->prefixFunctionsWithCSourceFileName($cSourceFile, $cSourceCode); 103 | 104 | $cSourceCodeMap[$classNameCSourceFileKey] = $cSourceCode; 105 | $withCSourceCode = "%{\n".$cSourceCodeMap[$classNameCSourceFileKey]."\n}%\n".$originalCode; 106 | $this->postSearchAndReplace($originalCode, $withCSourceCode); 107 | } 108 | } 109 | 110 | } 111 | 112 | } 113 | } 114 | 115 | } 116 | 117 | $currentClassCode = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 118 | $this->searchAndReplace($originalClassCode, $currentClassCode); 119 | } 120 | } 121 | 122 | private function prefixFunctionsWithCSourceFileName($cSourceFile, $cSourceCode) 123 | { 124 | //prepend file name on each defined c functions 125 | $cFunctionCallsToSearch = array(); 126 | $cFunctionCallsToReplace = array(); 127 | //we need to consider that cSourceFile might have / in it, in that case we need to change / to _ 128 | $prefix = explode(".",str_replace("/","_",$cSourceFile))[0]; 129 | $cSourceCode = preg_replace_callback("|[a-zA-Z0-9_]+[\s]*\(.*\)([\s]*){|",function($matches) use (&$prefix,&$cFunctionCallsToSearch, &$cFunctionCallsToReplace) { 130 | if (count($matches) > 0 && strlen($matches[0]) > 0) { 131 | //tricky, need to make sure it is not if, for and while 132 | $functionName = trim(substr($matches[0], 0, strpos($matches[0],"("))); 133 | if ( $functionName !== "for" && $functionName !== "while" && $functionName!== "if" ) { 134 | $cFunctionCallsToSearch[] = $functionName."("; 135 | $cFunctionCallsToReplace[] = $prefix."_".$functionName."("; 136 | return $prefix."_".$matches[0]; 137 | } else { 138 | return $matches[0]; 139 | } 140 | } 141 | },$cSourceCode); 142 | 143 | //now also change all subsequent function calls in context 144 | $cSourceCode = str_replace($cFunctionCallsToSearch, $cFunctionCallsToReplace, $cSourceCode); 145 | 146 | //now this will result in the pattern {prefix}_{prefix}_, we need to change that to {prefix}_ 147 | $cSourceCode = str_replace(" {$prefix}_{$prefix}_"," {$prefix}_", $cSourceCode); 148 | 149 | return $cSourceCode; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/ClassHierarchyFlatterningConverter.php: -------------------------------------------------------------------------------- 1 | getClassMap(); 12 | 13 | //now walk through the classMap and flattern out hierarchy 14 | foreach($classMap as $className => $classInfo) { 15 | $currentClassInfo = $classInfo; 16 | 17 | $currentClassCode = implode("\n",array_slice($this->codeLines, $currentClassInfo->startLine - 1, $currentClassInfo->endLine - $currentClassInfo->startLine + 1)); 18 | 19 | $currentClassProperties = $currentClassInfo->properties; 20 | $currentClassStaticProperties = $currentClassInfo->staticProperties; 21 | 22 | $currentClassMethodInfos = $currentClassInfo->methodInfos; 23 | 24 | $currentParentClass = isset($currentClassInfo->parentClass)?$currentClassInfo->parentClass:null; 25 | 26 | $injectedPropertiesCode = ""; 27 | $injectedMethodsCode = ""; 28 | 29 | while(TRUE) { 30 | if (!isset($currentClassInfo->parentClass)) { 31 | break; 32 | } 33 | 34 | $currentClassEndLine = $currentClassInfo->endLine; 35 | 36 | $parentClassInfo = $classMap[$currentClassInfo->parentClass]; //point current class info to the parent one 37 | 38 | //inject the properties that are defined in parent but not in current class 39 | $staticPropertiesToBeInjected = array_udiff($parentClassInfo->staticProperties, $currentClassInfo->staticProperties, function($a,$b){ 40 | return ($a->name === $b->name); 41 | }); 42 | 43 | $propertiesToBeInjected = array_udiff($parentClassInfo->properties, $currentClassInfo->properties, function($a,$b){ 44 | return ($a->name === $b->name); 45 | }); 46 | 47 | foreach($staticPropertiesToBeInjected as $propertyInfo) { 48 | $injectedPropertiesCode .= "\n".$propertyInfo->code."\n"; 49 | } 50 | 51 | foreach($propertiesToBeInjected as $propertyInfo) { 52 | $injectedPropertiesCode .= "\n".$propertyInfo->code."\n"; 53 | } 54 | 55 | foreach($parentClassInfo->methodInfos as $methodPureName => $methodInfo) { 56 | $methodCode = implode("\n",array_slice($this->codeLines, $methodInfo->startLine - 1, $methodInfo->endLine - $methodInfo->startLine + 1)); 57 | 58 | $selfReference = $methodInfo->isStatic?"\$self::":"\$this->"; 59 | 60 | $convertedMethodCode = $methodCode; 61 | 62 | if (!isset($currentClassMethodInfos[$methodPureName])) { //the current class does not have method defined, grab the parent version 63 | $currentClassMethodInfos[$methodPureName] = $methodCode; 64 | //now replace parent:: to __[namespace components] 65 | if (isset($parentClassInfo->parentClass)) { 66 | $convertedMethodCode = str_replace("parent::",$selfReference.strtolower(str_replace("\\","__",$parentClassInfo->parentClass))."_", $methodCode); 67 | } 68 | $injectedMethodsCode .= "\n".$convertedMethodCode."\n"; 69 | } 70 | 71 | $convertedMethodCode = str_replace("function ".$methodInfo->name, "function ".strtolower(str_replace("\\","__",$parentClassInfo->className)."_".$methodInfo->name), $methodCode); 72 | //now replace parent:: to __[namespace components] 73 | if (isset($parentClassInfo->parentClass)) { 74 | $convertedMethodCode = str_replace("parent::",$selfReference.strtolower(str_replace("\\","__",$parentClassInfo->parentClass))."_", $convertedMethodCode); 75 | } 76 | 77 | $injectedMethodsCode.= "\n".$convertedMethodCode."\n"; 78 | 79 | } 80 | 81 | $currentClassInfo = $parentClassInfo; 82 | } 83 | 84 | $newClassCode = $currentClassCode; 85 | if (strlen($injectedMethodsCode) > 0) { 86 | $currentClassCodeLines = explode("\n", $currentClassCode); 87 | //now remove any extends words so that each class is an independent unit!!! 88 | $currentClassCodeLines[0] = explode(" extends ", $currentClassCodeLines[0])[0]; 89 | $currentClassCodeLines[count($currentClassCodeLines) - 2] .= $injectedPropertiesCode."\n".$injectedMethodsCode."\n"; 90 | $newClassCode = implode("\n", $currentClassCodeLines); 91 | } 92 | 93 | if (strlen($currentParentClass) > 0) { 94 | //we still need to convert parent:: to $selfReference 95 | $newClassCode = str_replace("parent::",$selfReference.strtolower(str_replace("\\","__",$currentParentClass))."_", $newClassCode); 96 | } 97 | 98 | //convert all static to self 99 | $newClassCode = str_replace("static::","self::", $newClassCode); 100 | 101 | $this->searchAndReplace($currentClassCode, $newClassCode); 102 | 103 | //finally, in the zephir code, we need to replace {self}:: to self:: 104 | $this->postSearchAndReplace("{self}::","self::"); 105 | $this->postSearchAndReplace(" static("," self("); 106 | 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/CodeReformatConverter.php: -------------------------------------------------------------------------------- 1 | getClassMap(); 12 | 13 | foreach($classMap as $className => $classInfo) { 14 | 15 | $originalClassContent = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 16 | 17 | $content = ""; 18 | $abstract = $classInfo->isAbstract ? "abstract " : ""; 19 | $content .= $abstract."class ".array_pop(explode("\\",$classInfo->className))."\n"; 20 | $content .= "{\n"; 21 | 22 | ///////// Static Properties //////////// 23 | foreach($classInfo->staticProperties as $propertyInfo) { 24 | $content .= $propertyInfo->code."\n"; 25 | } 26 | 27 | //////// Instance Properties ////////// 28 | foreach($classInfo->properties as $propertyInfo) { 29 | $content .= $propertyInfo->code."\n"; 30 | } 31 | 32 | //////// Methods ////////// 33 | foreach($classInfo->methodInfos as $methodInfo) { 34 | $content .= implode("\n",array_slice($this->codeLines, $methodInfo->startLine - 1, $methodInfo->endLine - $methodInfo->startLine + 1)); 35 | $content .= "\n"; 36 | } 37 | 38 | $content .= "}\n"; 39 | $this->searchAndReplace($originalClassContent, $content); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/ForLoopToWhileLoopConverter.php: -------------------------------------------------------------------------------- 1 | codeASTXMLLines as $line) { 11 | if (strpos(trim($line), "") === 0) { 12 | $forLoopInfoIndexes[] = $index; 13 | } 14 | $index ++; 15 | } 16 | 17 | $forLoopInfos = array(); 18 | foreach($forLoopInfoIndexes as $index) { 19 | $startLineInfo = $this->codeASTXMLLines[$index + 2]; 20 | $endLineInfo = $this->codeASTXMLLines[$index + 5]; 21 | $forLoopInfo = new \stdClass(); 22 | $forLoopInfo->startLine = (int)str_replace(array("",""),"",$startLineInfo); 23 | $forLoopInfo->endLine = (int)str_replace(array("",""),"",$endLineInfo); 24 | $forLoopInfo->originalCode = trim(implode("\n", array_slice($this->codeLines, $forLoopInfo->startLine-1, $forLoopInfo->endLine - $forLoopInfo->startLine + 1))); 25 | $forLoopInfo->bodyCode = trim(implode("\n",array_slice($this->codeLines, $forLoopInfo->startLine, $forLoopInfo->endLine - $forLoopInfo->startLine - 1))); 26 | $forLoopStartLineCode = $this->codeLines[$forLoopInfo->startLine - 1]; 27 | $forLoopIndentationCode = explode("for",$forLoopStartLineCode)[0]; 28 | preg_match('/(?<=\()(.+)(?=\))/is', $forLoopStartLineCode, $match); 29 | $forLoopParenthesisCode = $match[0]; 30 | $forLoopParenthesisCodeComps = explode(";", $forLoopParenthesisCode); 31 | $forLoopInfo->initCode = trim($forLoopParenthesisCodeComps[0]); 32 | $forLoopInfo->conditionCode = trim($forLoopParenthesisCodeComps[1]); 33 | $forLoopInfo->incrementalCode = trim($forLoopParenthesisCodeComps[2]); 34 | $convertedWhileLoopCode = $forLoopInfo->initCode 35 | .";\n" 36 | .$forLoopIndentationCode 37 | ."while({$forLoopInfo->conditionCode})\n" 38 | .$forLoopIndentationCode 39 | .$forLoopIndentationCode 40 | .$forLoopInfo->bodyCode."\n" 41 | .$forLoopIndentationCode 42 | .$forLoopIndentationCode 43 | .$forLoopInfo->incrementalCode.";\n" 44 | .$forLoopIndentationCode 45 | ."}\n"; 46 | $forLoopInfo->convertedWhileLoopCode = $convertedWhileLoopCode; 47 | $forLoopInfos[] = $forLoopInfo; 48 | 49 | } 50 | 51 | foreach($forLoopInfos as $forLoopInfo) { 52 | $this->searchAndReplace($forLoopInfo->originalCode, $forLoopInfo->convertedWhileLoopCode); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/IssetToNotEmptyConverter.php: -------------------------------------------------------------------------------- 1 | searchAndReplace("isset","!empty"); 9 | $this->searchAndReplace("!!",""); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/ModuloCastingConverter.php: -------------------------------------------------------------------------------- 1 | codeLines as $index => $line) { 9 | $moduloSignPos = strpos($line, "%"); 10 | if ($moduloSignPos !== FALSE) { 11 | //make sure this is not some formatting string used in printf or fprintf 12 | $shouldContinue = true; 13 | for ($i = $moduloSignPos - 1; $i >= 0; $i--) { 14 | if ($line[$i] == "'" || $line[$i] == '"') { 15 | $shouldContinue = false; 16 | break; 17 | } 18 | } 19 | 20 | if ($shouldContinue) { 21 | //now back track the $ sign 22 | for ($i = $moduloSignPos - 1; $i >= 0; $i--) { 23 | if ($line[$i] == "$") { 24 | $originalCode = substr($line, $i, $moduloSignPos - $i + 1); 25 | $convertedCode = "(int)".$originalCode; 26 | $this->searchAndReplace($originalCode, $convertedCode); 27 | break; 28 | } 29 | } 30 | } 31 | 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/PrintToEchoConverter.php: -------------------------------------------------------------------------------- 1 | searchAndReplace("print ","echo "); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/SelfStaticConverter.php: -------------------------------------------------------------------------------- 1 | codeASTXMLLines as $index => $line) { 10 | if (strpos(trim($line), "") === 0) { 11 | $classMethodInfoIndexes[] = $index; 12 | } 13 | } 14 | 15 | foreach($classMethodInfoIndexes as $index) { 16 | $selfStaticVarNamesMap = array(); 17 | $startLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 2]); 18 | $endLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$index + 5]); 19 | //now search for all self::$ pattern 20 | for ($i = $startLine + 1; $i <= $endLine - 1; $i++) { 21 | //we need to find out the self static variable name here! 22 | preg_match("/self::\\$[a-zA-Z_]+/", $this->codeLines[$i], $matches); 23 | if (count($matches) == 1) { 24 | $selfStaticVarName = str_replace(array("self::","$"),"",$matches[0]); 25 | $selfStaticVarNamesMap[$selfStaticVarName] = 1; 26 | } 27 | } 28 | if (count($selfStaticVarNamesMap) > 0) { 29 | $originalClassMethodCode = trim(implode("\n", array_slice($this->codeLines, $startLine-1, $endLine - $startLine + 1))); 30 | foreach($selfStaticVarNamesMap as $varName => $value) { 31 | //now we will construct the static var init statement 32 | $selfStaticVarNameInitStmt = "\n"."$"."self__static__".$varName."=null;\n"; 33 | $this->codeLines[$startLine] .= $selfStaticVarNameInitStmt; 34 | //add post convertion searches and replaces 35 | //tricky, these must come in order 36 | $this->postSearchAndReplace("var self__static__$varName;",""); 37 | $this->postSearchAndReplace("let self__static__$varName = null;",""); 38 | $this->postSearchAndReplace("self__static__".$varName,"self::".$varName); 39 | $this->postSearchAndReplace("self::$varName,",""); 40 | $this->postSearchAndReplace(",self::$varName",""); 41 | } 42 | $convertedClassMethodCode = trim(implode("\n", array_slice($this->codeLines, $startLine-1, $endLine - $startLine + 1))); 43 | $convertedClassMethodCode = str_replace("self::$","\$self__static__", $convertedClassMethodCode); 44 | $this->searchAndReplace($originalClassMethodCode, $convertedClassMethodCode); 45 | } 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/StaticVarAutoDefineConverter.php: -------------------------------------------------------------------------------- 1 | GetClassMap(); 9 | foreach($classMap as $className => $classInfo) { 10 | 11 | $classPropertyStartLine = $classInfo->startLine + 1; 12 | $classPropertyEndLine = $classInfo->endLine - 1; 13 | 14 | $classCode = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 15 | 16 | $staticVars = array(); 17 | 18 | $i = 0; 19 | foreach($classInfo->methodInfos as $methodPureName => $methodInfo) 20 | { 21 | $i++; 22 | 23 | if ($i == 1) { 24 | $classPropertyEndLine = $methodInfo->startLine - 1; 25 | } 26 | 27 | $methodCode = implode("\n",array_slice($this->codeLines, $methodInfo->startLine - 1, $methodInfo->endLine - $methodInfo->startLine + 1)); 28 | preg_match_all("/self::\\$[a-zA-Z_]+/", $methodCode, $matches); 29 | 30 | if (count($matches) > 0 && count($matches[0]) > 0) { 31 | $staticVars = array_unique($matches[0]); 32 | $staticVars = array_map(function($element){ 33 | $element = str_replace("self::$","",$element); 34 | return $element; 35 | }, $staticVars); 36 | } 37 | 38 | } 39 | 40 | $definedStaticVars = array(); 41 | foreach($classInfo->staticProperties as $propertyInfo) { 42 | $definedStaticVars[] = $propertyInfo->name; 43 | } 44 | 45 | $undefinedStaticVars = array(); 46 | $undefinedStaticVars = array_diff($staticVars, $definedStaticVars); 47 | 48 | $defineStaticVarStmt = ""; 49 | foreach($undefinedStaticVars as $var) { 50 | //by default, just auto define variables as protected 51 | $defineStaticVarStmt .= "protected static $".$var.";\n"; 52 | } 53 | 54 | $this->codeLines[$classPropertyStartLine - 1] .= "\n".$defineStaticVarStmt."\n"; 55 | 56 | $newClassCode = implode("\n",array_slice($this->codeLines, $classInfo->startLine - 1, $classInfo->endLine - $classInfo->startLine + 1)); 57 | 58 | $this->searchAndReplace($classCode, $newClassCode); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/PHPtoCExt/Converter/TraitMergingConverter.php: -------------------------------------------------------------------------------- 1 | codeASTXMLLines as $index => $line) { 13 | if (strpos(trim($line), "") === 0) { 14 | $traitInfoIndexes[] = $index; 15 | } 16 | $index ++; 17 | } 18 | 19 | $traitBodyMap = array(); 20 | foreach($traitInfoIndexes as $index) { 21 | $startLineInfo = $this->codeASTXMLLines[$index + 2]; 22 | $endLineInfo = $this->codeASTXMLLines[$index + 5]; 23 | $startLine = (int)str_replace(array("",""),"",$startLineInfo); 24 | $endLine = (int)str_replace(array("",""),"",$endLineInfo); 25 | $traitName = trim(str_replace(array("",""), "", $this->codeASTXMLLines[$index + 8])); 26 | 27 | $namespace = $this->backtraceNamespaceFromLine($index); 28 | //now the traitName should consider the namespace it belongs to also 29 | $traitName = "\\".$namespace."\\".$traitName; 30 | 31 | $traitCode = trim(implode("\n", array_slice($this->codeLines, $startLine-1, $endLine - $startLine + 1))); 32 | $traitBodyMap[$traitName] = trim(implode("\n", array_slice($this->codeLines, $startLine + 1, $endLine - $startLine - 2))); 33 | 34 | //remove trait code 35 | $this->searchAndReplace($traitCode, ""); 36 | } 37 | 38 | $traitUseIndexes = array(); 39 | foreach($this->codeASTXMLLines as $index => $line) { 40 | if (strpos(trim($line), "") === 0) { 41 | $traitUseIndexes[] = $index; 42 | } 43 | $index ++; 44 | } 45 | 46 | foreach($traitUseIndexes as $index) { 47 | $startLineInfo = $this->codeASTXMLLines[$index + 2]; 48 | $endLineInfo = $this->codeASTXMLLines[$index + 5]; 49 | $startLine = (int)str_replace(array("",""),"",$startLineInfo); 50 | $endLine = (int)str_replace(array("",""),"",$endLineInfo); 51 | $traitUseCode = trim(implode("\n", array_slice($this->codeLines, $startLine-1, $endLine - $startLine + 1))); 52 | $traitsUsing = array_map(function($element){ 53 | return trim($element); 54 | },explode(",",str_replace(array("use ",";"),"",$traitUseCode))); 55 | 56 | $traitActualCode = ""; 57 | foreach($traitsUsing as $traitName) { 58 | 59 | if ($traitName[0] !== "\\") { //this is referencing a trait with relative namespace 60 | $namespace = $this->backtraceNamespaceFromLine($index); 61 | $traitName = "\\".$namespace."\\".$traitName; 62 | } 63 | 64 | if (!isset($traitBodyMap[$traitName])) { 65 | throw new \PHPtoCExt\PHPtoCExtException("using undefined trait ".$traitName." in code: ".$traitUseCode); 66 | } 67 | 68 | $traitActualCode .= $traitBodyMap[$traitName]."\n"; 69 | } 70 | 71 | $this->searchAndReplace($traitUseCode, $traitActualCode); 72 | 73 | } 74 | 75 | } 76 | 77 | private function backtraceNamespaceFromLine($line) 78 | { 79 | $result = ""; 80 | 81 | //now we have the traitName, we will go back and search for the namespace that this trait belongs to 82 | for ($i = $line - 1; $i >= 0; $i--) { 83 | if (strpos(trim($this->codeASTXMLLines[$i]),"") === 0) { 84 | $namespaceStartLine = (int)str_replace(array("",""),"",$this->codeASTXMLLines[$i + 2]); 85 | $result = trim(str_replace(array("namespace ",";"),"",$this->codeLines[$namespaceStartLine - 1])); 86 | break; 87 | } 88 | } 89 | 90 | return $result; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/PHPtoCExt/FileAnalyser.php: -------------------------------------------------------------------------------- 1 | file = $file; 14 | $this->fileContent = file_get_contents($file); 15 | $this->requireFile($file); 16 | } 17 | 18 | public function getNamespaceOfClass($class) 19 | { 20 | $classComps = explode("\\", $class); 21 | if (count($classComps) == 1) { 22 | throw new PHPtoCExtException("class $class should have a namespace!"); 23 | } 24 | array_pop($classComps); 25 | $namespace = ""; 26 | foreach($classComps as $index => $comp) { 27 | if ( ctype_upper($comp) || ctype_lower($comp) ) { 28 | throw new PHPtoCExtException("namespace must be in the CamelCase form!"); 29 | } 30 | 31 | if ($index == 0) { 32 | $namespace .= $comp; 33 | } else { 34 | $namespace .= "\\".$comp; 35 | } 36 | } 37 | return $namespace; 38 | } 39 | 40 | public function getRootNamespaceOfClass($class) 41 | { 42 | $namespace = $this->getNamespaceOfClass($class); 43 | $rootNamespace = array_shift(explode("\\",$namespace)); 44 | return $rootNamespace; 45 | } 46 | 47 | public function getCodeInClass($className) 48 | { 49 | $class = new \ReflectionClass($className); 50 | $startLine = $class->getStartLine()-1; // getStartLine() seems to start after the {, we want to include the signature 51 | $endLine = $class->getEndLine(); 52 | $numLines = $endLine - $startLine; 53 | $namespace = $this->getNamespaceOfClass($className); 54 | $classCode = "namespace $namespace;\n\n".implode("\n",array_slice(explode("\n",$this->fileContent),$startLine,$numLines))."\n"; 55 | return $classCode; 56 | } 57 | 58 | public function getClassNameWithoutNamespace($className) 59 | { 60 | return array_pop(explode("\\", $className)); 61 | } 62 | 63 | public function getUserDefinedClasses() 64 | { 65 | return $this->userDefinedClasses; 66 | } 67 | 68 | private function requireFile($file) 69 | { 70 | $previousDefinedClasses = get_declared_classes(); 71 | $previousDefinedInterfaces = get_declared_interfaces(); 72 | require_once $this->file; 73 | $currentDefinedClasses = get_declared_classes(); 74 | $currentDefinedInterfaces = get_declared_interfaces(); 75 | 76 | $userDefinedClasses = array_diff($currentDefinedClasses, $previousDefinedClasses); 77 | $userDefinedInterfaces = array_diff($currentDefinedInterfaces, $previousDefinedInterfaces); 78 | 79 | //now treat classes and interfaces as the same 80 | $this->userDefinedClasses = array_merge($userDefinedClasses, $userDefinedInterfaces); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/PHPtoCExt/FileFilter.php: -------------------------------------------------------------------------------- 1 | sourceFile = $sourceFile; 21 | $this->targetFile = $targetFile; 22 | $this->inputDir = $inputDir; 23 | $this->postSearches = array(); 24 | $this->postReplaces = array(); 25 | } 26 | 27 | public function filter() 28 | { 29 | $sourceFileContent = trim(file_get_contents($this->sourceFile)); 30 | 31 | //first, remove all comments in file content 32 | $sourceFileContent = $this->removeAllComments($sourceFileContent); 33 | $sourceFileContent = $this->putBracketsInNewLine($sourceFileContent); 34 | $sourceFileContent = $this->removeBlankLines($sourceFileContent); 35 | 36 | $parser = new \PhpParser\Parser(new \PhpParser\Lexer); 37 | $serializer = new \PhpParser\Serializer\XML(); 38 | 39 | try { 40 | //load all converters, order is very important 41 | $converterClasses = array( 42 | "\PHPtoCExt\Converter\TraitMergingConverter", 43 | "\PHPtoCExt\Converter\ForLoopToWhileLoopConverter", 44 | "\PHPtoCExt\Converter\PrintToEchoConverter", 45 | "\PHPtoCExt\Converter\ModuloCastingConverter", 46 | "\PHPtoCExt\Converter\IssetToNotEmptyConverter", 47 | "\PHPtoCExt\Converter\ClassHierarchyFlatterningConverter", 48 | "\PHPtoCExt\Converter\StaticVarAutoDefineConverter", 49 | "\PHPtoCExt\Converter\SelfStaticConverter", 50 | "\PHPtoCExt\Converter\CodeReformatConverter", //reformat the code and get ready for the final conversion 51 | "\PHPtoCExt\Converter\CFunctionAutoConverter", //convert c_function_auto to c_function_call 52 | "\PHPtoCExt\Converter\CFunctionCallConverter", //convert c function calls 53 | ); 54 | 55 | $searches = array(); 56 | $replaces = array(); 57 | $postSearches = array(); 58 | $postReplaces = array(); 59 | //go through all converters to convert the source code 60 | foreach ($converterClasses as $converterClass) { 61 | $stmts = $parser->parse($sourceFileContent); 62 | $codeLines = explode("\n", $sourceFileContent); 63 | $codeASTXML = $serializer->serialize($stmts); 64 | $codeASTXMLLines = explode("\n", $codeASTXML); 65 | 66 | $this->codeLines = $codeLines; 67 | $this->codeASTXMLLines = $codeASTXMLLines; 68 | 69 | $this->converter = new $converterClass($codeLines, $codeASTXMLLines, $this->inputDir); 70 | $this->converter->convert(); 71 | $searches = $this->converter->getSearches(); 72 | $replaces = $this->converter->getReplaces(); 73 | $sourceFileContent = str_replace($searches, $replaces, $sourceFileContent); 74 | 75 | $postSearches = array_merge($postSearches, $this->converter->getPostSearches()); 76 | $postReplaces = array_merge($postReplaces, $this->converter->getPostReplaces()); 77 | } 78 | 79 | file_put_contents($this->targetFile, $sourceFileContent); 80 | 81 | //add post searches and replaces 82 | $this->postSearches = $postSearches; 83 | $this->postReplaces = $postReplaces; 84 | 85 | } catch (\PhpParser\Error $e) { 86 | throw new PHPtoCExtException("PHP Parser Error: ".$e->getMessage()); 87 | } 88 | 89 | } 90 | 91 | public function postFilter($file) 92 | { 93 | $content = file_get_contents($file); 94 | 95 | $searchCount = count($this->postSearches); 96 | 97 | for ($i = 0; $i < $searchCount; $i++) { 98 | $content = str_replace($this->postSearches[$i], $this->postReplaces[$i], $content); 99 | } 100 | 101 | $content = $this->removeBlankLines($content); 102 | file_put_contents($file, $content); 103 | } 104 | 105 | public function getCodeLines() 106 | { 107 | return $this->codeLines; 108 | } 109 | 110 | public function getCodeASTXMLLines() 111 | { 112 | return $this->codeASTXMLLines; 113 | } 114 | 115 | public function getInputDir() 116 | { 117 | return $this->inputDir; 118 | } 119 | 120 | public function getClassMap() 121 | { 122 | return $this->converter->getClassMap(); 123 | } 124 | 125 | /** 126 | * remove all comments in php code 127 | */ 128 | private function removeAllComments($content) 129 | { 130 | //first remove all multiline comments 131 | $content = preg_replace('!/\*.*?\*/!s', '', $content); 132 | $content = preg_replace('/\n\s*\n/', "\n", $content); 133 | 134 | //now it is time to remove all single line comments 135 | $lines = explode("\n",$content); 136 | foreach($lines as $index => $line) { 137 | $line = preg_replace('~#[^\r\n]*~','',$line); 138 | $line = preg_replace('~//[^\r\n]*~','',$line); 139 | $line = preg_replace('~/\*.*?\*/~s','',$line); 140 | $lines[$index] = $line; 141 | } 142 | $result = implode("\n",$lines); 143 | return $result; 144 | } 145 | 146 | private function putBracketsInNewLine($content) 147 | { 148 | $lines = explode("\n",$content); 149 | $result = ""; 150 | foreach($lines as $index => $line) { 151 | $indentLevel = strlen($line) - strlen(ltrim($line)); 152 | $indentation = str_repeat(" ", $indentLevel); 153 | $lines[$index] = str_replace(array("{","}"), array("\n".$indentation."{", "\n".$indentation."}"), $line); 154 | } 155 | 156 | $result = implode("\n", $lines); 157 | 158 | return $result; 159 | } 160 | 161 | private function removeBlankLines($content) 162 | { 163 | //now remove all blank lines, credit: http://stackoverflow.com/questions/709669/how-do-i-remove-blank-lines-from-text-in-php 164 | $content = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $content); 165 | return $content; 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/PHPtoCExt/PHPtoCExtException.php: -------------------------------------------------------------------------------- 1 |