<?php

/*************************************************************************
 * PHP Closures                                                          *
 * Copyright (c) 2007, Peter Goodman                                     *
 *                                                                       *
 * Permission is hereby granted, free of charge, to any person obtaining *
 * a copy of this software and associated documentation files (the       *
 * "Software"), to deal in the Software without restriction, including   *
 * without limitation the rights to use, copy, modify, merge, publish,   *
 * distribute, sublicense, and/or sell copies of the Software, and to    *
 * permit persons to whom the Software is furnished to do so, subject to *
 * the following conditions:                                             *
 *                                                                       *
 * The above copyright notice and this permission notice shall be        *
 * included in all copies or substantial portions of the Software.       *
 *                                                                       *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND                 *
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS   *
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN    *
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN     *
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE      *
 * SOFTWARE.                                                             *
 *************************************************************************/

// make sure register globals is off
ini_set('register_globals'0);

// high error reporting
error_reporting(E_ALL);

// I need closure!

define ('CLOSURE_FN_DIR'dirname(__FILE__) .'/lambdas/');

// a class to deal with calling and compiling the lambdas
// and sub lambdas
class Lambda
{
    
protected $vars = array();
    
protected $_vars = array();
    
protected $_call_result;
    
    
protected $body '';
    
protected $id '';
    
protected $compiled_file '';
    
protected $args '';
    
protected $inner FALSE;
    
    
// constructor
    
public function __construct($vars = array(), $args ''$body ''$inner FALSE)
    {
        
// set the parent scope variables
        
$this->vars =& $vars;
        
$this->_vars array_flip(array_keys($this->vars)); // annoying but necessary hack.
        
        // set the argument string
        
$this->args $args;
        
        
// is this a lambda within a lambda?
        
$this->inner $inner;
        
        
// deal with the body
        
$this->scope($body);
    }
    
    
// destructor
    
public function __destruct()
    {
        unset(
$this->vars);
    }
    
    
// the function that the body of the function is
    // passed to
    
protected function scope($body)
    {
        
$this->body $body;        
        
        
// figure out an appropriate ID for this function
        // and the file to compile it to.
        
$this->id md5($this->args $this->body);
        
$this->compiled_file CLOSURE_FN_DIR $this->id .'.php';
        
        if(!
$this->isCompiled())
            
$this->compile();
    }
    
    
// call the closure
    
public function storeCall()
    {
        
$args func_get_args();
                
        
$this->_call_result call_user_func_array(array(&$this'call'), $args);
        
        return 
$this;
    }
    
public function extrAndCall()
    {
        return 
$this->_call_result;
    }
    
public function call()
    {
        
$ret NULL;
        
        
// is this closure compiled?
        
if($this->isCompiled())
        {            
            
// preserves references :)
            
extract($this->varsEXTR_REFS);
            
            
// include the file
            
include $this->compiled_file;
            
            
// get the arguments passed to this function
            
$args func_get_args();
            
            
// call the anonymous function
            
$ret call_user_func_array('fn'$this->id$args);
        }
        
        return 
$ret;
    }
    
    
// here comes the hacks!
    
public function __call($method$arguments)
    {
        
// do nothing
        
return $this;
    }
    
    
public function __get($key)
    {
        return 
$this;
    }
    
    
public function __set($key$val)
    {
        
//return $this->get($key, $val);
        
$ret NULL;
        if(
method_exists(&$this$key))
            if(
is_array($val))
                
$ret call_user_func_array(array(&$this$key), $val);
            else
                
$ret $this->$key($val);
        return 
$ret;
    }
    
    
// parse over the variables within the function
    // and look for ones that match those of the
    // parent scope
    
protected function parseVariables($matches)
    {
        
$ret $matches[1];
        
        if(isset(
$this->_vars[$ret]))
            
$ret '__scope->'$ret;
        
        return 
'$'$ret;
    }
    
// correct the form of sub lambdas when compiling.
    
protected function parseSubLambda($matches)
    {
        return 
'lambda(get_defined_vars(),\''str_replace("'""\'"trim($matches[1], ' ,')) .'\',\'';
    }
    
// parse assignment by reference with scope variables
    // and remove the reference symbol.
    
protected function parseVarRefAssigns($matches)
    {
        return 
'$__scope->'$matches[1] .'=';
    }
    
    
// has this closure been compiled?
    
protected function isCompiled()
    {
        return 
file_exists($this->compiled_file);
    }
    
    
// compile the lambda
    
protected function compile()
    {
        
// parse out the special nature of short-hand sub-lambdas
        // fix single-quotes that *were* escaped but no longer are
        
$this->body str_replace("'""\'"$this->body);
        
$this->body preg_replace_callback("~lambda\(([^{]*){~", array(&$this'parseSubLambda'), $this->body);
        
$this->body str_replace('})''\',TRUE)'$this->body);
                
        
// figure out the function name
        
$fn_name 'fn'$this->id;
        
        
$nl "\n";
        
        
// create the PHP code for our compiled function
        
$buffer '<?php'$nl;
        
$buffer .= 'if(!class_exists("Lambda")) exit;'$nl;
        
$buffer .= '$__stack = ScopeStack::getInstance();'$nl;
        
$buffer .= '$__stack->pushScope(new LambdaScope);'$nl;
        
$buffer .= '$__scope = $__stack->last();'$nl;
        
        
// reference scope variables
        
foreach($this->_vars as $key => $val)
            
$buffer .= '$__scope->'$key .'=$'$key.';'$nl;
                
        
// create the function
        
$buffer .= 'if(!function_exists("'$fn_name .'"))'$nl;
        
$buffer .= '{'$nl;
        
$buffer .= '    function '$fn_name .'('$this->args .')'$nl;
        
$buffer .= '    {'$nl;
        
$buffer .= '        $__stack = ScopeStack::getInstance();'$nl;
        
$buffer .= '        $__scope = $__stack->last();'$nl;
        
$buffer .= preg_replace_callback('~\$([a-z0-9_]*)~i', array(&$this"parseVariables"), $this->body) . $nl;
        if(!
$this->inner)
            
$buffer .= '        $__stack->popScope();'$nl;
        
$buffer .= '    }'$nl;
        
$buffer .= '}'$nl;
        
$buffer .= '?>';
        
        
// now get rid of assignment by reference with scope
        // variables.
        
$buffer preg_replace_callback('~\$__scope->([^=]*)=([\s]*)&~i', array(&$this'parseVarRefAssigns'), $buffer);
        
        
// finish off the propogate (propagate?) hack
        
$buffer str_replace('->call(''->storeCall('$buffer);
        
        
// we only want to perform this operation once and apply all
        // necessary changes to all inner lambdas
        
if(!$this->inner)
        {
            
$stack = array();
            
$matches preg_split("~(\(|\))~"$buffer, -1PREG_SPLIT_DELIM_CAPTURE);
            
$buffer '';
            
            
// go through the buffer and push and pop ( and ) on/off a
            // stack.
            
for($i 0$i count($matches); $i++)
            {
                
$str $matches[$i];
                
$buff $str;
                
                
// push onto the stack
                
if($str == '(')
                    if(isset(
$matches[$i-1]) && strpos($matches[$i-1],'storeCall') !== FALSE)
                        
$stack[] = TRUE;
                    else
                        
$stack[] = FALSE;
                
                
// pop off the stack
                
else if($str == ')')
                {
                    
// this is a closing ) for a 'storeCall' function
                    
$ret array_pop($stack);
                    if(
$ret !== FALSE)
                        
$buff ')->extrAndCall(extract(ScopeStack::getInstance()->popScope(),EXTR_OVERWRITE))';
                }
                
                
$buffer .= $buff;
            }
        }
        
        
// write the compiled closure to a file
        
$fp fopen($this->compiled_file"w+");
        
        if(
$fp)
        {
            if(
fwrite($fp$buffer) === FALSE)
                
throw new Exception('Could not compile closure.');
            
            
fclose($fp);
        }
    }
}

// a stack to deal with the transfer of variables
// also happens to be a singleton
class ScopeStack
{
    
// static instance of this class
    
static public $inst;
    
    
private $stack = array();
    
private $key = -1;
    
    
private function __construct()
    {
        
// can't be accessed by outside :)
    
}
    
public function __destruct()
    {
        unset(
$this->stack);
    }
    
    
// singleton method
    
static public function &getInstance()
    {
        if(
self::$inst === NULL)
            
self::$inst = new self;
        
        return 
self::$inst;
    }
    
    
public function pushScope($scope)
    {        
        
$this->key++;
                
        
$this->stack[] = $scope;
    }
    
    
public function &popScope()
    {
        
$this->key--;
                
        
$scope =& array_pop($this->stack);
        
        
$this->pop_scope FALSE;
        
        return 
$scope->__vars;
    }
    
    
public function last()
    {
        
$ret NULL;
        if(isset(
$this->stack[$this->key]))
            
$ret $this->stack[$this->key];
        
        return 
$ret;
    }
}

// a class for each function that holds the parent
// scope variables.
class LambdaScope
{
    
// the parent scope variables
    
public $__vars = array();
    
    
// my own set function so that I can pass
    // vars by reference
    
public function __set($name$val)
    {
        
$this->__vars[$name] =& $val;
        
        return 
TRUE;
    }
    
    
// overloading... 
    
public function &__get($name)
    {
        
$ret NULL;
        
        if(
$this->__isset($name))
            
$ret =& $this->__vars[$name];
        
        return 
$ret;
    }
    
public function __isset($name)
    {
        return isset(
$this->__vars[$name]);
    }
    
public function __unset($name)
    {
        unset(
$this->__vars[$name]);
    }
}

// create a closure
// *** NEVER USE $inner! ***
function lambda($vars = array(), $args ''$body ''$inner FALSE)
{
    
// we don't need all this crud
    
unset($vars['GLOBALS'], $vars['_SERVER'],     $vars['HTTP_SERVER_VARS'], 
                            
$vars['_POST'],     $vars['HTTP_POST_VARS'], 
                            
$vars['_GET'],         $vars['HTTP_GET_VARS'], 
                            
$vars['_ENV'],         $vars['HTTP_ENV_VARS'], 
                            
$vars['_COOKIE'],     $vars['HTTP_COOKIE_VARS'], 
                            
$vars['_FILES'],     $vars['HTTP_POST_FILES'], 
                            
$vars['_REQUEST'],
                            
$vars['PHPSESSID'],
                            
$vars['__scope'],
                            
$vars['__stack']);
        
    
// create the closure!
    
return new Lambda($vars$args$body$inner);
}

/*
// Yay! True LAMBDA!

$func = lambda(array(), '$new_test', '
    static $test;
    if($test === NULL)
    {
        lambda({
            $test = $new_test;
        })->call();
    }
    else
    {
        echo $test;
    }
');

$func->call('hello');
$func->call('');

//$func->call('');
*/
/*

lambda(get_defined_vars(), '', '
    $b = lambda($a, {
        echo $a{0};
    });
    $c = lambda($d, {
        $d->call("hi");
    });
    
    $c->call($b);
')->call();

// outputs: h
*/
/*
// fixed-point combinator
function Y(Lambda &$le) 
{
    $v = get_defined_vars();
    
    return lambda($v, 'Lambda $f', '
        return $f->call($f);
    ')->call(lambda($v, 'Lambda $f', '
        return $le->call(lambda($x, {
            return $f->call($f)->call($x);
        }));
    '));
}

// anonymous factorial function
$factorial =& Y(lambda(array(), 'Lambda $fac', '
    return lambda($n, {
        return $n <= 2 ? $n : $n * $fac->call($n - 1);
    });
'));

echo $factorial->call(5);

// output: 120
*/
/*

Javascript Version:

function Y(le) {
    return function (f) {
        return f(f);
    }(function (f) {
        return le(function (x) {
            return f(f)(x);
        });
    });
}

var factorial = Y(function (fac) {
    return function (n) {
        return n <= 2 ? n : n * fac(n - 1);
    };
});

var number120 = factorial(5);

*/
/*
class Foo
{
    public $bar = 'hi';
}

$foo = new Foo;
$foo1 = new Foo;
//var_dump($foo);
//var_dump($foo1);
$anon = lambda(get_defined_vars(), '', '
    $foo->bar = "hello";
');

$anon->call();

var_dump($foo);

// gives: object(Foo)#1 (1) { ["bar"]=>  string(5) "hello" }
*/

?>