<?php
interface IErrorManager {
public function ariseFatal($message);
public function ariseWarning($message);
public function ariseNotice($message);
}
interface ILexer {
public function tokenize($input);
}
interface IParser {
public function setKeywords(array $keywords);
public function parse($input);
}
interface IParseResult {
public function getDefinition();
public function getName();
public function getConstants();
}
interface INameValidator {
public function validate($name);
}
interface ICompiler {
public function compile($input);
}
class ErrorManager implements IErrorManager {
public function ariseFatal($message) {
$this->triggerError($message, E_USER_FATAL);
}
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);
}
}
abstract class CompilerElement {
private $errorManager;
protected function getErrorManager() {
return $this->errorManager;
}
protected function __construct(IErrorManager $errorManager) {
$this->errorManager = $errorManager;
}
}
class Lexer extends CompilerElement implements ILexer {
public function tokenize($input) {
if (!is_string($input)) {
$this->getErrorManager()->ariseFatal(get_class($this).'::parse expects parameter 1 to be string. '.gettype($input).' given.');
}
}
public function __construct(IErrorManager $errorManager) {
parent::__construct($errorManager);
}
}
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.');
}
$errorManager = $this->getErrorManager();
$input = $this->cleanUp($input);
$tokens = $this->lexer->tokenize($input);
$result = array();
$currentResult = null;
$processingBody = false;
reset($tokens);
while (($token = key($tokens)) !== false && ($tokenValue = current($tokens)) !== false) {
switch ($token) {
case 'type' && $processingBody === false:
if ($currrentResult !== null || !isset($this->keywords[$tokenValue])) {
$errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue);
}
$currentResult = new ParseResult($this->keywords[$tokenValue], $errorManager);
break;
case 'name' && $processingBody === false:
// Will this ever happen?
if ($currentResult === null) {
$errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue);
}
$this->nameValidator->validate($name);
$currentResult->setName($name);
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) {
$value = $tokenValue !== null ? $tokenValie : 0;
$currentResult->setItem($token, $value);
} else {
$errorManager->ariseFatal('Syntax error. Unexpected '.$tokenValue);
}
break;
}
next($tokens);
}
return $return;
}
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;
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) === false) ? (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;
}
}
class NameValidator extends CompilerElement implements INameValidator {
private $allowedChars;
public function validate($name) {
if (!is_string($name)) {
$this->gerErrorManager()->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->gerErrorManager()->ariseFatal('Name should start with alphabetic characters. Given name '.$name.' starts with: '.$current);
}
if (!in_array($current, $this->allowedChars, true)) {
$this->gerErrorManager()->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 Enum2PhpCompiler implements ICompiler {
private $parser;
const INTENDATION_CHAR = "\t";
public function compile($input) {
$parseResult = $this->parser->parse($input);
$return = $parseResult->getDefinition().' '.$parseResult->getName().' {'.PHP_EOL;
foreach ($parseResult->getConstants() as $name => $value) {
$return .= Compiler::INTENDATION_CHAR.'const '.$name.' = '.$value.PHP_EOL;
}
$return .= Compiler::INTENDATION_CHAR.'private function __construct() {}'.PHP_EOL;
$return .= '}';
return $return;
}
public function __construct(IParser $parser) {
$this->parser = $parser;
}
}
$code = <<<PHP
enum MyEnum {
Item1,
Item2 = 5,
Item3
}
PHP;
preferences:
26.32 ms | 402 KiB | 5 Q