<?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);
}
}
- Output for 5.3.0 - 5.3.29, 5.4.0 - 5.4.45, 5.5.0 - 5.5.38, 5.6.0 - 5.6.28, 7.0.0 - 7.0.20, 7.1.0 - 7.1.33, 7.2.0 - 7.2.33, 7.3.0 - 7.3.33, 7.4.0 - 7.4.33, 8.0.0 - 8.0.30, 8.1.0 - 8.1.28, 8.2.0 - 8.2.18, 8.3.0 - 8.3.6
preferences:
224.75 ms | 405 KiB | 330 Q