├── LICENSE ├── README.md ├── composer.json ├── composer.lock └── src ├── Pool.php ├── Trampoline.php └── functions.php /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2016, Gilles Crettenand 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trampoline 2 | 3 | [![Build Status](https://travis-ci.org/functional-php/trampoline.svg)](https://travis-ci.org/functional-php/trampoline) 4 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/functional-php/trampoline/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/functional-php/trampoline/?branch=master) 5 | [![Code Coverage](https://scrutinizer-ci.com/g/functional-php/trampoline/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/functional-php/trampoline/?branch=master) 6 | [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/functional-php/trampoline.svg)](http://isitmaintained.com/project/functional-php/trampoline "Average time to resolve an issue") 7 | [![Percentage of issues still open](http://isitmaintained.com/badge/open/functional-php/trampoline.svg)](http://isitmaintained.com/project/functional-php/trampoline "Percentage of issues still open") 8 | [![Chat on Gitter](https://img.shields.io/gitter/room/gitterHQ/gitter.svg)](https://gitter.im/functional-php) 9 | 10 | Trampolines are a technique used to avoid blowing the call stack when doing recursive calls. This is needed because PHP does not perform tail-call optimization. 11 | 12 | For more information about what is tail-call optimization (or TCO), you can read : http://stackoverflow.com/questions/310974/what-is-tail-call-optimization#answer-310980 13 | 14 | For a more in depth definition of trampolines and recursion as a whole, I can recommend you read http://blog.moertel.com/posts/2013-06-12-recursion-to-iteration-4-trampolines.html which is using Python but should be easy enough to understand. 15 | 16 | ## Installation 17 | 18 | composer require functional-php/trampoline 19 | 20 | ## Basic Usage 21 | 22 | If we have the following recursive function: 23 | 24 | ```php 25 | =5.6.0" 14 | }, 15 | "require-dev": { 16 | "atoum/atoum": "*" 17 | }, 18 | "scripts": { 19 | "test": "./vendor/bin/atoum --enable-branch-and-path-coverage" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "FunctionalPHP\\Trampoline\\": "./src" 24 | }, 25 | "files": [ 26 | "src/functions.php" 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "fa85b093f9a3b24d21cc4a54de800fd8", 8 | "content-hash": "dd1f0ee41bff90f5547d51c68c9b3f54", 9 | "packages": [], 10 | "packages-dev": [ 11 | { 12 | "name": "atoum/atoum", 13 | "version": "2.8.2", 14 | "source": { 15 | "type": "git", 16 | "url": "https://github.com/atoum/atoum.git", 17 | "reference": "4d0136b21185eea5fc2ee638f77b291e6c537100" 18 | }, 19 | "dist": { 20 | "type": "zip", 21 | "url": "https://api.github.com/repos/atoum/atoum/zipball/4d0136b21185eea5fc2ee638f77b291e6c537100", 22 | "reference": "4d0136b21185eea5fc2ee638f77b291e6c537100", 23 | "shasum": "" 24 | }, 25 | "require": { 26 | "ext-hash": "*", 27 | "ext-json": "*", 28 | "ext-session": "*", 29 | "ext-tokenizer": "*", 30 | "ext-xml": "*", 31 | "php": ">=5.3.3" 32 | }, 33 | "replace": { 34 | "mageekguy/atoum": "*" 35 | }, 36 | "suggest": { 37 | "atoum/stubs": "Provides IDE support (like autocompletion) for atoum", 38 | "ext-mbstring": "Provides support for UTF-8 strings" 39 | }, 40 | "bin": [ 41 | "bin/atoum" 42 | ], 43 | "type": "library", 44 | "extra": { 45 | "branch-alias": { 46 | "dev-master": "2.x-dev" 47 | } 48 | }, 49 | "autoload": { 50 | "classmap": [ 51 | "classes/" 52 | ] 53 | }, 54 | "notification-url": "https://packagist.org/downloads/", 55 | "license": [ 56 | "BSD-3-Clause" 57 | ], 58 | "authors": [ 59 | { 60 | "name": "Frédéric Hardy", 61 | "email": "frederic.hardy@atoum.org", 62 | "homepage": "http://blog.mageekbox.net" 63 | }, 64 | { 65 | "name": "François Dussert", 66 | "email": "francois.dussert@atoum.org" 67 | }, 68 | { 69 | "name": "Gérald Croes", 70 | "email": "gerald.croes@atoum.org" 71 | }, 72 | { 73 | "name": "Julien Bianchi", 74 | "email": "julien.bianchi@atoum.org" 75 | }, 76 | { 77 | "name": "Ludovic Fleury", 78 | "email": "ludovic.fleury@atoum.org" 79 | } 80 | ], 81 | "description": "Simple modern and intuitive unit testing framework for PHP 5.3+", 82 | "homepage": "http://www.atoum.org", 83 | "keywords": [ 84 | "TDD", 85 | "atoum", 86 | "test", 87 | "unit testing" 88 | ], 89 | "time": "2016-08-12 13:45:10" 90 | } 91 | ], 92 | "aliases": [], 93 | "minimum-stability": "stable", 94 | "stability-flags": [], 95 | "prefer-stable": true, 96 | "prefer-lowest": false, 97 | "platform": [], 98 | "platform-dev": [] 99 | } 100 | -------------------------------------------------------------------------------- /src/Pool.php: -------------------------------------------------------------------------------- 1 | f = $f->bindTo($this); 23 | } elseif(method_exists('\Closure','fromCallable')) { 24 | $this->f = \Closure::fromCallable($f)->bindTo($this); 25 | } else { 26 | throw new \RuntimeException('Using anything else than a callable is only possible for PHP >= 7.1.'); 27 | } 28 | } 29 | 30 | /** 31 | * Invoke the stored function with the stored arguments. 32 | * 33 | * @return mixed 34 | */ 35 | public function __invoke() 36 | { 37 | $result = null; 38 | $this->arguments_pool[] = func_get_args(); 39 | 40 | if($this->recursing === false) { 41 | $this->recursing = true; 42 | 43 | while(! empty($this->arguments_pool)) { 44 | $result = call_user_func_array($this->f, array_shift($this->arguments_pool)); 45 | } 46 | 47 | $this->recursing = false; 48 | } 49 | 50 | return $result; 51 | } 52 | 53 | /** 54 | * @param callable $f 55 | * @return callable 56 | */ 57 | public static function get(callable $f) 58 | { 59 | return new static($f); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Trampoline.php: -------------------------------------------------------------------------------- 1 | f = $f; 20 | $this->args = $args; 21 | } 22 | 23 | /** 24 | * Invoke the stored function with the stored arguments. 25 | * 26 | * @return mixed 27 | */ 28 | public function __invoke() 29 | { 30 | return call_user_func_array($this->f, $this->args); 31 | } 32 | 33 | /** 34 | * Create a new trampoline instance for the given function and arguments. 35 | * 36 | * @param callable $f 37 | * @param array ...$args 38 | * @return static|callable 39 | */ 40 | public static function bounce(callable $f, ...$args) 41 | { 42 | return new static($f, $args); 43 | } 44 | 45 | /** 46 | * Run a callable or a Trampoline until it gets to the final result 47 | * (ie: not a Trampoline instance) 48 | * 49 | * @param callable|Trampoline $f 50 | * @param array ...$args 51 | * @return mixed 52 | */ 53 | public static function run($f, ...$args) 54 | { 55 | if($f instanceof self) { 56 | $return = $f; 57 | } else if(is_callable($f)) { 58 | $return = call_user_func_array($f, $args); 59 | } else { 60 | throw new \RuntimeException('Expected a callable or an instance of Trampoline.'); 61 | } 62 | 63 | while($return instanceof self) { 64 | $return = $return(); 65 | } 66 | 67 | return $return; 68 | } 69 | 70 | /** 71 | * Helper function to easily run a callable as a Trampoline. 72 | * 73 | * @param string $name 74 | * @param array $arguments 75 | * @return mixed 76 | */ 77 | public static function __callStatic($name, $arguments) 78 | { 79 | return static::run($name, ...$arguments); 80 | } 81 | } -------------------------------------------------------------------------------- /src/functions.php: -------------------------------------------------------------------------------- 1 |