'(?:\w*(?:\.\w*)?)', 'value' => '(?:(?:"[^"]*")|[\-+]?\d*(?:\.\d*)?|true|false)', 'var_value' => '(?:\w*(?:\.\w*)?|(?:"[^"]*")|[\-+]?\d*(?:\.\d*)?|true|false)' ); private $data = array(); private $php = ''; private static function render_var($name = null, $safe = true){ $var = '$' . self::$var_name . ($name ? '[\'' . join('\'][\'', explode('.', $name)) . '\']' : ''); return $safe ? '(isset(' . $var . ')?' . $var . ':null)' : $var; } private static function split_values($values, $delimiter = '\s*,\s*'){ return preg_split('@(' . $delimiter . ')(?=(?:[^"]|"[^"]*")*$)@', $values); } private static function parse_values($values, $delimiter = '\s*,\s*', $safe = true){ $value_bits = self::split_values($values, $delimiter); foreach($value_bits as $k => $value) { $value_bits[$k] = self::parse_value($value, $safe); } return $value_bits; } private static function parse_boolean($data){ if( !preg_match( '@(' . self::$regex['var_value'] . ')\s*(?:(is(?:(?:\s*not|n\'?t)?\s*(?:(?:greater|lower)(?:\s*than)?|equal(?:\s*to)?))?|has(?:\s*not)?)\s*(' . self::$regex['var_value'] . '))?@', $data, $bits ) ) { return ''; } $fn = array( 'is' => function($data, $var1, $var2){ $symbols = array( '' => '===', 'equal' => '==', 'lower' => '<', 'greater' => '>' ); preg_match('@(?not)?\s*(?equal|lower|greater)?\s*(?:to|than)?@', $data, $bits); return (isset($bits['not']) && $bits['not'] !== '' ? '!': '') . '(' . $var1 . ' ' . $symbols[isset($bits['operation']) ? $bits['operation']: ''] . ' ' . $var2 . ')'; }, 'has' => function($data, $var1, $var2){ return ($data === 'not' ? '!': '') . 'array_key_exists((array)' . $var1 . ', ' . $var2 . ')'; } ); if(isset($bits[3])) { $ops = explode(' ', $bits[2], 2); return $fn[$ops[0]](isset($ops[1]) ? $ops[1]: '', self::parse_value($bits[1]), self::parse_value($bits[3])); } else { return self::parse_value($bits[1]); } } private static function parse_value($value, $safe = true){ return preg_match('@^' . self::$regex['value'] . '$@', $value) ? $value : self::render_var($value, $safe); } function __construct($str){ if(!self::$var_name) { self::$var_name = 'DATA' . mt_rand() . time(); } // http://stackoverflow.com/a/1320156 $this->php = <<<'PHP' $FN = array( 'array_flat' => function(array $array) { $r = array(); array_walk_recursive($array, function($a)use(&$r){$r[]=$a;}); return $r; }, 'inc' => function(&$_, $by = 1){ switch(gettype($_)) { case 'integer': case 'double': $_ += $by; break; case 'string': for($i = 0; $i < $by; $i++) { ++$_; } break; } } ); PHP; $brackets = 0; $replacement = array( '/' => function()use(&$brackets){ if($brackets > 0) { --$brackets; return '}'; } else { return ''; } }, '//' => function(){ return ''; }, 'echo' => function($data){ preg_match('@^(?:separator\s+(?' . self::$regex['var_value'] . ')\s+)?(?.*)$@', $data, $bits); $separator = $bits['separator'] ? self::parse_value($bits['separator']): '\'\''; return 'echo implode(' . $separator . ', $FN[\'array_flat\']((array)' . implode(')), implode(' . $separator . ', $FN[\'array_flat\']((array)', self::parse_values($bits['data'])) . '));'; }, 'echol' => function($data)use(&$replacement){ return $replacement['echo']($data) . 'echo PHP_EOL;'; }, 'echoj' => function($data)use(&$replacement){ return $replacement['echo']('separator ' . $data); }, 'echojl' => function($data)use(&$replacement){ return $replacement['echol']('separator ' . $data); }, 'echof' => function($data)use(&$replacement){ return $replacement['echol']('separator ' . $data); }, 'print' => function($data)use(&$replacement){ return $replacement['call']((strpos('into', $data)===0? 's' : '') . 'printf ' . $data); }, 'if' => function($data)use(&$brackets){ ++$brackets; return 'if(' . self::parse_boolean($data) . '){'; }, 'else' => function($data)use(&$brackets, &$replacement){ $data = explode(' ', $data, 2); if($data[0] === 'if') { --$brackets; return '}else ' . $replacement['if']($data[1]); } else { return '}else{'; } }, 'each' => function($data)use(&$brackets){ ++$brackets; preg_match('@^(?' . self::$regex['var'] . ')\s*as\s*(?' . self::$regex['var'] . ')(?:\s*key\s*(?' . self::$regex['var'] . ')\s*)?$@', $data, $bits); return 'foreach((array)' . self::render_var($bits['var']) . ' as ' . (isset($bits['key']) ? self::render_var($bits['key'], false) . ' => ': '') . self::render_var($bits['as'], false) . '){'; }, 'while' => function($data)use(&$brackets){ ++$brackets; return 'while(' . self::parse_boolean($data) . '){'; }, 'for' => function($data)use(&$brackets){ ++$brackets; return preg_replace_callback( '@(?' . self::$regex['var'] . ')(?:\s*from\s*(?' . self::$regex['var_value'] . '))?(?:\s*to\s*(?' . self::$regex['var_value'] . '))(?:\s*step\s*(?' . self::$regex['var_value'] . '))?@', function($matches){ $values = array( 'start' => isset($matches['start']) && $matches['start'] !== '' ? self::parse_value($matches['start']) : '0', 'end' => isset($matches['end']) ? self::parse_value($matches['end']) : self::parse_value($matches['start']), 'step' => isset($matches['step']) ? self::parse_value($matches['step']) : '1' ); return 'foreach(range(' . $values['start'] . ', ' . $values['end'] . ', abs(' . $values['step'] . ')) as ' . self::render_var($matches['var'], false) . '){'; }, $data ); }, 'set' => function($data){ preg_match('@^(?' . self::$regex['var'] . ')\s*(?.*?)$@', $data, $bits); $values = self::parse_values($bits['values']); if(count($values) > 1) { return self::render_var($bits['var'], false) . ' = array(' . implode(',', $values) . ');'; } else { return self::render_var($bits['var'], false) . ' = ' . self::parse_value($bits['values']) . ';'; } }, 'global' => function($data){ $data = self::split_values($data, ' '); return self::render_var(array_shift($data)) . ' = $GLOBALS[\'' . join('\'][\'', $data) . '\'];'; }, 'call' => function($data){ preg_match('@^\s*(?\w+)\s*(?:into\s*(?' . self::$regex['var'] . ')\s*)?(?.*?)$@', $data, $data); return ($data['into'] ? self::render_var($data['into'], false) . ' = ' : '') . $data['fn'] . '(' . implode(',', self::parse_values($data['args'])) . ');'; }, 'php' => function($data){ return '?> function($data){ return 'return ' . ($data ? self::parse_value($data): '').';'; }, 'inc' => function($data){ preg_match('@^(?:\s*by\s*(?' . self::$regex['var_value'] . ')\s*)?(?.*?)$@', $data, $bits); $values = self::parse_values($bits['values'], '\s*,\s*', false); $inc = isset($bits['by']) && $bits['by'] !== '' ? self::parse_value($bits['by']): '1'; $return = ''; foreach($values as $value) { if(!isset($value[0]) || $value[0] !== '$') { continue; } $return .= <<php .= str_replace( array( "echo <<<'" . self::$var_name . "'\r\n\r\n" . self::$var_name . ";", "\r\n" . self::$var_name . ";\r\necho <<<'" . self::$var_name . "'" ), array('', ''), "echo <<<'" . self::$var_name . "'\r\n" . preg_replace_callback( // http://stackoverflow.com/a/6464500 '~{@(echoj?l?|print|if|else|for|while|each|set|call|global|php|return|inc|//?)(?:\\s*(.*?))?}(?=(?:[^"\\\\]*(?:\\\\.|"(?:[^"\\\\]*\\\\.)*[^"\\\\]*"))*[^"]*$)~i', function($matches)use(&$replacement){ return "\r\n" . self::$var_name . ";\r\n" . $replacement[$matches[1]](isset($matches[2]) ? $matches[2] : null) . "echo <<<'" . self::$var_name . "'\r\n"; }, $str . '' ) . "\r\n" . self::$var_name . ";\r\n" ) . str_repeat('}', $brackets); } function setData($key, $value){ $this->data[$key] = $value; } function getData($key, $value){ return isset($this->data[$key]) ? $this->data[$key] : null; } function unsetData($key){ unset($this->data[$key]); } function loadData($data){ foreach($data as $k => $value) { $this->data[$k] = $value; } } function clearData(){ $this->data = array(); } function getPHP(){ $this->data['argv'] = func_get_args(); $this->data['argc'] = func_num_args(); $this->data['VERSION'] = self::$version; $this->data['EOL'] = PHP_EOL; return '$DATA = ' . var_export($this->data, true) . ';$' . self::$var_name . ' = &$DATA;' . $this->php; } function render(){ $fn = eval('return function(){' . call_user_func_array(array($this, 'getPHP'), func_get_args()) . '};'); return $fn(); } static function fromFile($path){ return new self(file_get_contents($path)); } static function fromString($string){ return new self($string); } }