Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
406 lines (360 sloc) 14.2 KB
<?php /* Generated by LadyPHP */
/**
* LadyPHP - type PHP with elegance
* @link http://github.com/unu/ladyphp
* @author unumin
* @license http://sam.zoy.org/wtfpl/COPYING
*/
/**
* Converts LadyPHP code to PHP and works as stream wrapper.
*/
class Lady{
const REGEX_CLASS = ';^([A-Z].*|self|parent)$;';
const REGEX_VARIABLE = ';^[_a-z].*$;';
const REGEX_NOVARIABLE = ';^(false|true|self|parent|null)$;';
const JOINING = '&& || & | -> . + - , / * % = ? :';
const ENDING = ' ; { } ( array( [ <?'; # do not add ; { } after these
const CONTINUING = ') ]'; # line is continuing if starts with these
const CONCATED = 'T_STRING T_CONSTANT_ENCAPSED_STRING T_ENCAPSED_AND_WHITESPACE T_CLASS_C T_DIR T_FILE T_FUNC_C T_METHOD_C T_NS_C T_TRAIT_C';
var $buffer, $position, $filename, $cacheFile;
static $head = '/* Generated by LadyPHP */';
static $cacheDir;
/**
* Loads file and parses it.
* @param string File to parse
* @param bool Expanded style
* @return string PHP code
*/
static function parseFile($filename, $expanded = false){
return self::parse(file_get_contents($filename), $expanded);}
/**
* Parses LadyPHP from string to PHP code
* @param string LadyPHP code
* @param bool Expanded style
* @return string PHP code
*/
static function parse($source, $expanded = false){
$source = str_replace("\r", '', $source);
$openingBracket = false;
$closingBrackets = array();
$anonymousBrackets = array();
$closingIndentStr = array();
$squareBrackets = 0;
$arrayBrackets = array();
# process tokens
$tokens = self::tokenize($source);
foreach ($tokens as $n => $token){
$token = $tokens[$n];
extract($token, EXTR_OVERWRITE | EXTR_REFS);
# skip last dummy token
if ($n > count($tokens) - 2){
break;}
# square brackets to array()
elseif (!$type && $str == '['){
$squareBrackets++;
if ($hasBlank || in_array($tokens[$n - 1]['str'], explode(' ', self::JOINING . ' ' . self::ENDING))){
$str = 'array(';
$arrayBrackets[$squareBrackets] = true;}}
elseif (!$type && $str == ']'){
if (isset($arrayBrackets[$squareBrackets]) && $arrayBrackets[$squareBrackets]){
$str = ')';
$arrayBrackets[$squareBrackets] = false;}
$squareBrackets--;}
# convert 'fn' to 'function'
elseif ($type == T_STRING && $str == 'fn'){
$str = 'function';
if ($tokens[$n + 1]['str'] == '('){
$anonymousBrackets[] = $indent;}}
# convert . to -> or :: and .. to .
elseif (!$type && $str == '.'){
if ($tokens[$n + 1]['str'] == '.'){
$tokens[$n + 1]['str'] = '';}
elseif (!$hasBlank || !$tokens[$n + 1]['hasBlank']){
if (preg_match(self::REGEX_CLASS, $tokens[$n - 1]['str'])){
$str = '::';}
else{
$str = '->';}}}
# convert : to =>
elseif (!$type && $str == ':' && !$hasBlank && !$isLast){
$str = ' =>';}
# add $ before variables
elseif ($type == T_STRING
&& $tokens[$n + 1]['str'] != '('
&& $tokens[$n - 1]['str'] != '->'
&& preg_match(self::REGEX_VARIABLE, $str)
&& !preg_match(self::REGEX_NOVARIABLE, $str)){
$str = '$' . $str;}
# add . between variables and strings
if (!$isFirst && $hasBlank
&& (in_array(token_name($type), explode(' ', self::CONCATED))
|| (!$type && in_array($str, array('"', '@'))))
&& (in_array(token_name($tokens[$n - 1]['type']), explode(' ', self::CONCATED))
|| in_array($tokens[$n - 1]['str'], array(')', ']'))
|| (!$tokens[$n - 1]['type'] && $tokens[$n - 1]['str'] == '"'))
&& !in_array($tokens[$n - 1]['str'], array('function'))){
$tokens[$n - 1]['str'] .= ' .';}
# add 'new' before 'Foo\Bar()'
$i = 0;
while ((($tokens[$n + $i]['type'] == T_STRING
&& preg_match(self::REGEX_CLASS, $tokens[$n + $i]['str']))
|| $tokens[$n + $i]['type'] == T_NS_SEPARATOR)
&& !in_array($tokens[$n - 1]['type'], array(T_STRING, T_NS_SEPARATOR))
&& (!$tokens[$n + $i]['hasBlank'] || $i == 0)
&& $tokens[$n - 1]['type'] != T_NEW){
if ($tokens[$n + $i]['type'] == T_STRING
&& $tokens[$n + $i + 1]['str'] == '('){
$str = 'new ' . $str;
break;}
$i++;}
# add semicolon and brackets
if ($isLast && !in_array($type, array(T_OPEN_TAG, T_OPEN_TAG_WITH_ECHO, T_CLOSE_TAG))
&& !in_array($str, explode(' ', self::JOINING . ' ' . self::ENDING))){
# sort list of closing brackets
$closingBrackets = array_unique($closingBrackets);
rsort($closingBrackets);
$anonymousBrackets = array_unique($anonymousBrackets);
rsort($anonymousBrackets);
# switch block
$isSwitch = false;
$i = 0;
while (isset($tokens[$n - $i]['y'])
&& $tokens[$n - $i]['y'] == $y){
if (in_array($tokens[$n - $i]['type'], array(T_CASE, T_DEFAULT))){
$isSwitch = true;
break;}
$i++;}
if ($isSwitch){
$str .= ':';}
# next line is indented
elseif ($tokens[$n + 1]['indent'] > $indent){
# add opening bracket
if (!in_array($tokens[$n + 1]['str'], explode(' ', self::JOINING . ' ' . self::CONTINUING))){
$str .= '{';
$closingBrackets[] = $indent;
$closingIndentStr[$indent] = $indentStr;
$openingBracket = false;}
# save opening bracket
else{
$openingBracket = $indent;}}
# line doesn't continue
elseif (!in_array($tokens[$n + 1]['str'], explode(' ', self::JOINING . ' ' . self::CONTINUING))
|| $closingBrackets[0] >= $tokens[$n + 1]['indent']){
# there is saved opening bracket
if ($openingBracket !== false
&& $tokens[$n + 1]['indent'] > $openingBracket){
$str .= '{';
$closingBrackets[] = $openingBracket;
$openingBracket = false;}
# add semicolon
else{
$str .= ';';}}
# add closing brackets
if ($indent > $tokens[$n + 1]['indent']){
while (isset($closingBrackets[0])
&& $closingBrackets[0] >= $tokens[$n + 1]['indent']){
if ($expanded){
$str .= "\n" . $closingIndentStr[$closingBrackets[0]];}
$str .= '}';
if (isset($anonymousBrackets[0]) && $anonymousBrackets[0] == $closingBrackets[0]){
$anonymousBrackets = array_slice($anonymousBrackets, 1);
if (!in_array($tokens[$n + 1]['str'], explode(' ', self::JOINING . ' ' . self::CONTINUING))){
$str .= ';';}}
$closingBrackets = array_slice($closingBrackets, 1);}}}
# convert php open tags
if ($type == T_OPEN_TAG){
if (!strstr($str, 'php')){
$str = str_replace('?', '?php ', $str);}
if ($y == 0){
$str = str_replace('php', 'php ' . self::$head, $str);}}
if ($type == T_OPEN_TAG_WITH_ECHO){
$str = str_replace('<?=', '<?php echo ', $str);}
# save token
$tokens[$n] = $token;}
# glue code
$code = null;
foreach ($tokens as $token){
$code .= $token['blank'] . $token['str'];}
# return
return $code;}
/**
* Tokenizes source code and extends tokens.
* @param string LadyPHP code
* @return array Tokens
*/
static function tokenize($source){
# replace open short tags to full ones
if (!function_exists('ini_get') || !ini_get('short_open_tag')){
do{
$changed = false;
$tokens = token_get_all($source);
$source = null;
foreach ($tokens as $n => $token){
$token = is_array($token) ? $token : array(null, $token);
if (!$changed && $token[0] == T_INLINE_HTML){
if (strpos($token[1], '<?=') !== false){
$position = strpos($token[1], '<?=');
$source .= substr_replace($token[1], '<?php echo ', $position, 3);
$changed = true;}
elseif (strpos($token[1], '<?') !== false){
$position = strpos($token[1], '<?');
$source .= substr_replace($token[1], '<?php ', $position, 2);
$changed = true;}
else{
$source .= $token[1];}}
else{
$source .= $token[1];}}}
while ($changed);}
# prepare tokens
$tokens = array();
$blank = null;
foreach (token_get_all($source) as $n => $token){
# convert to associative array
if (is_array($token)){
$token = array('str' => $token[1], 'type' => $token[0]);}
else{
$token = array('str' => $token, 'type' => null);}
# save whitespaces and comments into tokens
if (in_array($token['type'], array(T_COMMENT, T_DOC_COMMENT, T_WHITESPACE, T_INLINE_HTML))){
$blank .= $token['str'];}
else{
$token['blank'] = $blank;
$token['hasBlank'] = ($blank != null);
$blank = null;
$tokens[] = $token;}}
# save remaining blank
$tokens[] = array('str' => null, 'type' => null, 'blank' => $blank, 'isLast' => true);
# get positions
foreach ($tokens as $n => $token){
$token['n'] = $n;
if ($n == 0){
$token['indent'] = $token['x'] = $token['y'] = 0;
$token['isFirst'] = true;}
else{
$blankRow = array_slice(explode("\n", $token['blank']), -1);
$token['y'] = $tokens[$n - 1]['y'] + count(explode("\n", $tokens[$n - 1]['str'] . $token['blank'])) - 1;
$token['isFirst'] = $tokens[$n - 1]['isLast'] = ($tokens[$n - 1]['y'] != $token['y']);
$token['x'] = mb_strlen($blankRow[0]);
$token['x'] += !$token['isFirst'] ? $tokens[$n - 1]['x'] + mb_strlen($tokens[$n - 1]['str']) : null;
$token['indent'] = $token['isFirst'] ? $token['x'] : $tokens[$n - 1]['indent'];
$token['indentStr'] = $token['isFirst'] ? $blankRow[0] : $tokens[$n - 1]['indentStr'];}
$tokens[$n] = $token;}
return $tokens;}
/**
* Converts changed or new .lady files in directory to .php files.
* @param string Directory containing .lady files
* @param bool Recursive search
* @param bool Expanded style
* @return array List of converted files
*/
static function convert($dir, $recursive = false, $expanded = false){
$files = array();
$it = new RecursiveDirectoryIterator(realpath($dir));
if ($recursive){
$it = new RecursiveIteratorIterator($it);}
while ($it->valid()){
if (!$it->isDot() && $it->isFile()
&& pathinfo($it->key(), PATHINFO_EXTENSION) == 'lady'){
$phpFile = substr($it->key(), 0, -5) . '.php';
if (!is_file($phpFile) || filemtime($phpFile) < filemtime($it->key())){
file_put_contents($phpFile, self::parseFile($it->key(), $expanded));
$files[] = $phpFile;}}
$it->next();}
return $files;}
/**
* Watches directory and converts changed or new .lady files to .php files.
* @param string Directory to watch
* @param bool Recursive search
* @param boll Expanded style
*/
static function watch($dir, $recursive = false, $expanded = false){
while (true){
$files = self::convert($dir, $recursive, $expanded);
foreach ($files as $file){
echo date("H:i:s") . ' ' . $file . "\n";}
usleep(500000);}}
/**
* Converts and requires lady file.
* @param string Filename
* @param bool Require file once
* @return mixed Return value of required file
*/
static function requireFile($filename, $once = false){
if (!is_file($filename)){
throw new ErrorException('Required file ' . $filename . ' not found.');}
stream_wrapper_unregister('file');
stream_wrapper_register('file', __CLASS__);
$result = $once ? require_once($filename) : require($filename);
stream_wrapper_restore('file');
return $result;}
/**
* Opens file and uses cache.
* @param string Filename
* @return bool File was loaded
*/
function stream_open($filename){
stream_wrapper_restore('file');
$this->filename = realpath($filename);
$this->position = 0;
if (!is_file($this->filename)){
throw new ErrorException('File ' . $filename . ' not found.');}
if (!self::$cacheDir){
self::$cacheDir = sys_get_temp_dir() . '/ladyphp-' . sha1(realpath(__FILE__));}
if (!is_dir(self::$cacheDir)){
mkdir(self::$cacheDir, 0755, true);}
$this->cacheFile = self::$cacheDir . '/' . sha1($this->filename) . '.php';
if (!is_file($this->cacheFile) || filemtime($this->cacheFile) <= filemtime($this->filename)){
$this->buffer = self::parseFile($this->filename);
file_put_contents($this->cacheFile, $this->buffer);}
else{
$this->buffer = file_get_contents($this->cacheFile);}
return is_string($this->buffer);}
/**
* Read bytes from file.
* @param int
* @return string
*/
function stream_read($count){
$this->position += $count;
return substr($this->buffer, $this->position - $count, $count);}
/**
* Returns true if file pionter is at EOF.
* @return bool
*/
function stream_eof(){
return $this->position >= strlen($this->buffer);}
/**
* Returns info about file.
* @return array
*/
function stream_stat(){
return array('size' => strlen($this->buffer), 'mode' => 0100644);}
/**
* Returns info about file.
* @return array
*/
function url_stat(){
return $this->stream_stat();}}
/**
* Shortcut functions
*/
if (!function_exists('lady')){
function lady($filename, $once = false){
return Lady::requireFile($filename, $once);}}
if (!function_exists('lady_once')){
function lady_once($filename, $once = true){
return Lady::requireFile($filename, $once);}}
/**
* Process parameters from command line
*/
if (isset($argv[1]) && realpath($argv[0]) == realpath(__FILE__)){
$opts = getopt('i:o:c::w::er');
if (isset($opts['i'])){
$output = Lady::parseFile($opts['i'], isset($opts['e']));
if (isset($opts['o'])){
file_put_contents($opts['o'], $output);}
else{
echo $output;}}
elseif (isset($opts['c'])){
echo implode("\n", Lady::convert($opts['c'], isset($opts['r']), isset($opts['e']))) . "\n";}
elseif (isset($opts['w'])){
Lady::watch($opts['w'], isset($opts['r']), isset($opts['e']));}}
Jump to Line
Something went wrong with that request. Please try again.