3v4l.org

run code in 150+ php & hhvm versions
Bugs & Features
<?php interface ILexer { /** * return associative array containing tokens for parser * @param string $input code to tokenize * @return array */ public function tokenize($input); } interface INameValidator { /** * Checks if name is valid. In a case of failure trigger syntax error. * @param string $name * @return void */ public function validate($name); } interface IParser { /** * Sets array of keywords. * @param array $keywords associative array where key is keyword and value respective php instruction. * @return void */ public function setKeywords(array $keywords); /** * Parses code. Checks for syntax errors. * If no error occurs returns data for compiler to assemble php class * otherwise trigger syntax error. * @param string $input code to parse. * @return array IParserResult */ public function parse($input); } interface IParseResult { public function getDefinition(); public function getName(); public function getConstants(); } interface ICompiler { /** * Compiles enum into valid php class * @param string $input code to compile. * @return string */ public function compile($input); } interface IErrorManager { /** * Triggers fatal error. * @param string $message error message * @return void */ public function ariseFatal($message); /** * Triggers warning. * @param string $message error message * @return void */ public function ariseWarning($message); /** * Triggers notice. * @param string $message error message * @return void */ public function ariseNotice($message); } //I know this lexer sucks. class Lexer extends CompilerElement implements ILexer { // Potential hidden dependency private $specialChars = array( '{' => 'start_body', '}' => 'end_body' ); public function tokenize($input) { if (!is_string($input)) { $this->getErrorManager()->ariseFatal(get_class($this).'::parse expects parameter 1 to be string. '.gettype($input).' given.'); } $tokens = array(); $token = $this->getEmptyToken(); $tokenMetadata = $this->getDefaultTokenMetadata(); $inputLength = strlen($input); for ($i = 0; $i < $inputLength; $i++) { $current = $input[$i]; //check if special character if (isset($this->specialChars[$current])) { $token[$this->specialChars[$current]] = $current; if ($i !== $inputLength - 1) continue; } if (isset($token['end_body'])) { $tokens[] = $token; $token = $this->getEmptyToken(); $tokenMetadata = $this->getDefaultTokenMetadata(); if ($i === $inputLength - 1) continue; } $isWhitespace = ctype_space($current); if (isset($token['start_body'])) { //skip if whitespace if ($isWhitespace) { continue; } if ($current === ',') { $tokenMetadata['newItem'] = true; $tokenMetadata['newItemNameResolved'] = false; continue; } if ($current === '=') { $tokenMetadata['newItemNameResolved'] = true; continue; } if ($tokenMetadata['newItem']) { $token['e_'.$current] = null; $tokenMetadata['newItem'] = false; } else { end($token); $lastKey = key($token); if (!$tokenMetadata['newItemNameResolved']) { unset($token[$lastKey]); $token[$lastKey.$current] = null; } else { $token[$lastKey] .= $current; } } continue; } if (!$tokenMetadata['typeResolved']) { if ($isWhitespace) { if (strlen($token['type']) === 0) { continue; } else { $tokenMetadata['typeResolved'] = true; } } else { $token['type'] .= $current; } } else if (!$tokenMetadata['nameResolved']) { if ($isWhitespace) { if (strlen($token['name']) === 0) { continue; } else { $tokenMetadata['nameResolved'] = true; } } else { $token['name'] .= $current; } } } return $tokens; } private function getEmptyToken() { return array( 'type' => '', 'name' => '' ); } private function getDefaultTokenMetadata() { return array( 'typeResolved' => false, 'nameResolved' => false, 'newItem' => true, 'newItemNameResolved' => false ); } public function __construct(IErrorManager $errorManager) { parent::__construct($errorManager); } } class NameValidator extends CompilerElement implements INameValidator { private $allowedChars; public function validate($name) { if (!is_string($name)) { $this->getErrorManager()->ariseFatal(get_class($this).'::validate expects parameter 1 to be string. '.gettype($name).' given.'); } $nameLength = strlen($name); for ($i = 0; $i < $nameLength; $i++) { $current = $name[$i]; if ($i === 0 && !ctype_alpha($current)) { $this->getErrorManager()->ariseFatal('Name should start with alphabetic characters. Given name '.$name.' starts with: '.$current); } if (!in_array(strtolower($current), $this->allowedChars, true)) { $this->getErrorManager()->ariseFatal('Unexpected character '.$current.' in name '.$name.'.'); } } } public function __construct(IErrorManager $errorManager) { parent::__construct($errorManager); // Yes, I hate that everything is allowed as name in php. $this->allowedChars = str_split('abcdefghijklmnopqrstuvwxyz0123456789'); } } class Parser extends CompilerElement implements IParser { private $lexer; private $nameValidator; private $keywords = array(); public function setKeywords(array $keywords) { $this->keywords = $keywords; } public function parse($input) { if (!is_string($input)) { $this->getErrorManager()->ariseFatal(get_class($this).'::parse expects parameter 1 to be string. '.gettype($input).' given.'); } $input = $this->cleanUp($input); $errorManager = $this->getErrorManager(); $allTokens = $this->lexer->tokenize($input); $result = array(); $currentResult = null; $processingBody = false; foreach ($allTokens as $tokens) { foreach ($tokens as $token => $tokenValue) { switch ($token) { case 'type': if ($currentResult !== null || !isset($this->keywords[$tokenValue])) { $errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue); } $currentResult = new ParseResult($this->keywords[$tokenValue], $errorManager); break; case 'name': if ($currentResult === null) { $errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue); } $this->nameValidator->validate($tokenValue); $currentResult->setName($tokenValue); break; case 'start_body': if ($currentResult === null || $currentResult->getName() === null) { $errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue); } $processingBody = true; break; case 'end_body': if ($currentResult === null || $currentResult->getName() === null || $processingBody === false) { $errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue); } $result[] = $currentResult; $currentResult = null; $processingBody = false; break; default: if ($processingBody === true && $currentResult !== null) { $name = ltrim($token, 'e_'); $this->nameValidator->validate($name); if ($tokenValue === null) { $constants = $currentResult->getConstants(); $last = end($constants); $value = ($last !== false) ? $last + 1 : 0; } else { $value = $tokenValue; } $currentResult->setItem($name, $value); } else { $errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue); } break; } } } return $result; } private function cleanUp($input) { return trim($input); } public function __construct(ILexer $lexer, INameValidator $nameValidator, IErrorManager $errorManager) { parent::__construct($errorManager); $this->lexer = $lexer; $this->nameValidator = $nameValidator; } } class ParseResult extends CompilerElement implements IParseResult { private $definition; private $name; private $constants = array(); public function getDefinition() { return $this->definition; } public function getName() { return $this->name; } public function getConstants() { return $this->constants; } public function setItem($name, $value) { if (!is_string($name)) { $this->getErrorManager()->ariseFatal('Enumeration item name can only be string.'); } if (!is_numeric($value)) { $this->getErrorManager()->ariseFatal('Enumeration item can only hold numeric value.'); } $this->constants[$name] = (ctype_digit($value) === true) ? (int)$value : (double)$value; } public function setName($name) { if (!is_string($name)) { $this->getErrorManager()->ariseFatal(get_class($this).'::setName expects parameter 1 to be string. '.gettype($name).' given.'); } $this->name = $name; } public function __construct($definition, IErrorManager $errorManager) { parent::__construct($errorManager); if (!is_string($definition)) { $this->getErrorManager()->ariseFatal(get_class($this).'::__construct expects parameter 1 to be string. '.gettype($definition).' given.'); } $this->definition = $definition; } } abstract class CompilerElement { private $errorManager; protected function getErrorManager() { return $this->errorManager; } protected function __construct(IErrorManager $errorManager) { $this->errorManager = $errorManager; } } class Enum2PhpCompiler implements ICompiler { const INDENTATION_CHAR = "\t"; private $parser; public function compile($input) { $parseResult = $this->parser->parse($input); $return = ''; foreach ($parseResult as $result) { $return .= $this->generateClass($result); } return $return; } private function generateClass(IParseResult $parseResult) { $return = $parseResult->getDefinition().' '.$parseResult->getName().' {'.PHP_EOL; foreach ($parseResult->getConstants() as $name => $value) { $return .= Enum2PhpCompiler::INDENTATION_CHAR.'const '.$name.' = '.$value.';'.PHP_EOL; } $return .= Enum2PhpCompiler::INDENTATION_CHAR.'private function __construct() {}'.PHP_EOL; $return .= '}'.PHP_EOL; return $return; } public function __construct(IParser $parser) { $this->parser = $parser; $this->parser->setKeywords(array('enum' => 'final class')); } } class ErrorManager implements IErrorManager { public function ariseFatal($message) { $this->triggerError($message, E_USER_ERROR); } public function ariseWarning($message) { $this->triggerError($message, E_USER_WARNING); } public function ariseNotice($message) { $this->triggerError($message, E_USER_NOTICE); } private function triggerError($message, $type) { trigger_error((string)$message, (int)$type); } } $errorManager = ErrorManager(); $nameValidator = NameValidator($errorManager); $lexer = new Lexer($errorManager); $parser = new Parser($lexer, $nameValidator, $errorManager); $compiler = new Enum2PhpCompiler($parser); $enum = <<<PHP enum MyEnum { Item1 = 1, Item2 = 5, Item3 } enum AnotherEnum { Item } PHP; eval($compiler->compile($enum)); var_dump(MyEnum::Item1, MyEnum::Item2, MyEnum::Item3, AnotherEnum::Item);
Output for 7.0.0 - 7.1.0
Fatal error: Uncaught Error: Call to undefined function ErrorManager() in /in/QSkf7:433 Stack trace: #0 {main} thrown in /in/QSkf7 on line 433
Process exited with code 255.
Output for hhvm-3.12.0
Fatal error: Uncaught Error: Call to undefined function ErrorManager() in /in/QSkf7:433 Stack trace: #0 {main}
Process exited with code 255.
Output for 5.3.0 - 5.6.28, hhvm-3.10.0
Fatal error: Call to undefined function ErrorManager() in /in/QSkf7 on line 433
Process exited with code 255.