src/LJSON.php
<?php
namespace LJSON;
/**
* Class LJSON
* @package LJSON
*/
class LJSON
{
const RETURN_UNDEFINED_AS_SPECIAL_CLASS = 1073741824; // 2^30
/**
* @var bool
*/
public static $errorHandlerSet = false;
/**
* @return void
*/
public static function restoreErrorHandler()
{
if (static::$errorHandlerSet) {
static::$errorHandlerSet = false;
restore_error_handler();
}
}
/**
* @param mixed $value
* @param int $parameterCount
* @return string
* @throws \LJSON\StringifyException
* @api
* @example example/example1.php 15 1
*/
public static function stringify($value, $parameterCount = 0)
{
if (is_null($value) || is_bool($value)
|| is_int($value) || is_float($value) || is_double($value) || is_numeric($value)
|| is_string($value)
) {
return json_encode($value);
}
if ($value instanceof \stdClass) {
$value = (array)$value;
if (empty($value)) {
return '{}';
}
}
if ($value instanceof SpecialUndefinedIdentifierClass) {
return 'undefined';
}
if (is_object($value) && in_array("JsonSerializable", class_implements($value))) {
/**
* @var $value \JsonSerializable
*/
$value = $value->jsonSerialize();
}
if (is_array($value)) {
/**
* @var $value \array
*/
if ($value === []) {
return '[]';
}
foreach ($value as $key => $item) {
$value[$key] = static::stringify($item, $parameterCount);
}
if (array_keys($value) !== range(0, count($value) - 1)) {//isAssoc
$result = '{';
foreach ($value as $key => $v) {
$result .= '"' . $key . '":' . $v . ',';
}
return rtrim($result, ',') . '}';
}
return '[' . implode(',', $value) . ']';
}
if ($value instanceof Parameter) {
/**
* @var $value Parameter
*/
return $value->getParameterResult();
}
if (is_callable($value)) {
/**
* @var $value \callable
*/
$reflection = new \ReflectionFunction($value);
$params = [];
for ($i = 0; $i < count($reflection->getParameters()); $i++) {
$paramName = "v" . ($i + $parameterCount);
$params[$paramName] = new Parameter($paramName);
}
$parameterCount += count($params);
static::$errorHandlerSet = true;
$oldEH = function () {
};
$oldEH = set_error_handler(function ($severity, $message, $filename, $lineNumber) use (&$oldEH) {
$message = preg_replace(
"/Object of class LJSON\\\\Parameter could not be converted to (.*)/",
"Parameter's can not be converted (to $1)",
$message,
-1,
$count
);
if ($count) {
throw new StringifyException($message, $filename, $lineNumber, 1448007982);
}
try {
$oldEH($severity, $message, $filename, $lineNumber);
} catch (\Exception $e) {
LJSON::restoreErrorHandler();
throw $e;
}
});
$newValue = call_user_func_array($value, $params);
LJSON::restoreErrorHandler();
$value = static::stringify($newValue, $parameterCount);
return "(" . implode(',', array_keys($params)) . ") => (" . $value . ")";
}
throw new StringifyException('Type not supported ', __FILE__, __LINE__, 1448007971);
}
/**
* @param callable $library
* @param callable $function
* @return \Closure
* @api
*/
public static function withLib(callable $library, callable $function)
{
return function () use ($library, $function) {
return call_user_func_array($function, array_merge([$library], func_get_args()));
};
}
/**
* @param callable $function
* @return \Closure
* @api
*/
public static function withStdLib(callable $function)
{
$stdLibrary = function ($function, $parameter1, $parameter2 = null) {
switch ($function) {
case 'sqrt':
return sqrt($parameter1);
}
if ($parameter2 === null) {
return null;
}
switch ($function) {
case '+':
return $parameter1 + $parameter2;
case '-':
return $parameter1 - $parameter2;
case '*':
return $parameter1 * $parameter2;
case '/':
return $parameter1 / $parameter2;
}
return null;
};
return static::withLib($stdLibrary, $function);
}
/**
* @param callable $lib
* @param string $ljson
* @param bool|false $assoc
* @return \Closure
* @throws \Exception
* @api
*/
public static function parseWithLib(callable $lib, $ljson, $assoc = false)
{
return static::withLib($lib, static::parse($ljson, $assoc));
}
/**
* @param string $ljson
* @param bool|false $assoc
* @return \Closure
* @throws \Exception
* @api
*/
public static function parseWithStdLib($ljson, $assoc = false)
{
return static::withStdLib(static::parse($ljson, $assoc));
}
/**
* @param string $ljson
* @param bool|false $assoc
* @param int $options
* @return string|bool|int|float|null|array|\stdClass|\Closure
* @throws \Exception
* @api
* @example example/example1.php 16 1
*/
public static function parse($ljson, $assoc = false, $options = 0)
{
$pos = 0;
static::skipSpace($ljson, $pos);
$resultCode = static::parseValue($ljson, $pos, $assoc, [], $options);
static::skipSpace($ljson, $pos);
if ($pos == strlen($ljson) && $resultCode !== '') {
return static::evaly($resultCode);
}
throw new \Exception('Could not get Parsed', 1445505229);
}
/**
* @param $string
* @return mixed
* @throws \Exception
*/
protected static function evaly($string)
{
return eval('return ' . $string . ';');
}
/**
* @param string $chr
* @return bool
*/
protected static function isDigit($chr)
{
$chr = ord($chr[0]);
return $chr >= 48 && $chr <= 57;
}
/**
* @param string $string
* @param int $pos
* @param string $word
* @return bool
*/
protected static function isWord($string, $pos, $word)
{
return strlen($string) >= ($pos + strlen($word)) && substr($string, $pos, strlen($word)) === $word;
}
/**
* @param string $string
* @param int $pos
*/
protected static function skipSpace($string, &$pos)
{
$length = strlen($string);
while ($length > $pos && $string[$pos] && $string[$pos] <= ' ') {
$pos++;
}
}
/**
* @param string $json
* @param int $pos position in string
* @param bool|false $assoc
* @param array $variables
* @param int $options
* @return string
*/
protected static function parseValue($json, &$pos, $assoc = false, $variables = [], $options = 0)
{
$length = strlen($json);
$result = '';
//undefined
if (static::isWord($json, $pos, 'undefined')) {
$pos += 9;
if ($options & static::RETURN_UNDEFINED_AS_SPECIAL_CLASS) {
return 'new \LJSON\SpecialUndefinedIdentifierClass';
}
return "null";
}
//null
if (static::isWord($json, $pos, 'null')) {
$pos += 4;
return "null";
}
//true
if (static::isWord($json, $pos, 'true')) {
$pos += 4;
return "true";
}
//false
if (static::isWord($json, $pos, 'false')) {
$pos += 5;
return "false";
}
//number
if ($length > $pos && (static::isDigit($json[$pos]) || $json[$pos] == '-')) {
do {
$result .= $json[$pos];
$pos++;
} while ($length > $pos && static::isDigit($json[$pos]));
if ($length > $pos && $json[$pos] == '.' && static::isDigit($json[$pos + 1])) {
$result .= '.';
$pos++;
do {
$result .= $json[$pos];
$pos++;
} while ($length > $pos && static::isDigit($json[$pos]));
}
$exponent = '';
if ($length > $pos && strtolower($json[$pos]) == 'e') {
$exponent = 'e';
$pos++;
if ($length > $pos && $json[$pos] == '+') {
$exponent .= $json[$pos];
$pos++;
} elseif ($length > $pos && $json[$pos] == '-') {
$exponent .= $json[$pos];
$pos++;
}
do {
$exponent .= $json[$pos];
$pos++;
} while ($length > $pos && static::isDigit($json[$pos]));
}
$result .= $exponent;
return $result;
}
//string
if ($length > $pos && $json[$pos] == '"') {
$pos++;
$escape = [
'"' => '"',
'\\' => '\\',
'/' => '/',
'b' => "\b",
'f' => "\f",
'n' => "\n",
'r' => "\r",
't' => "\t"
];
while ($length > $pos && $json[$pos] != '"') {
if ($json[$pos] == '\\') {
$pos++;
if ($json[$pos] === 'u') {
$code = "&#" . hexdec(substr($json, $pos + 1, 4)) . ";";
$conVMap = [0x80, 0xFFFF, 0, 0xFFFF];
$result .= mb_decode_numericentity($code, $conVMap, 'UTF-8');
$pos += 5;
} elseif (isset($escape[$json[$pos]])) {
$result .= $escape[$json[$pos]];
$pos++;
}
continue;
}
$result .= $json[$pos];
$pos++;
}
$pos++;
return '"' . str_replace('"', '\"', $result) . '"';
}
//array
if ($length > $pos && $json[$pos] == '[') {
$pos++;
$elements = [];
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == ']') {
$pos++;
return '[]';
}
do {
static::skipSpace($json, $pos);
$elements[] = static::parseValue($json, $pos, $assoc, $variables, $options);
static::skipSpace($json, $pos);
} while ($length > $pos && $json[$pos] == ',' && $pos++);
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == ']') {
$pos++;
return '[' . implode(',', $elements) . ']';
}
}
//object
if ($length > $pos && $json[$pos] == '{') {
$pos++;
$elements = [];
do {
static::skipSpace($json, $pos);
$string = static::parseValue($json, $pos, $assoc, $variables, $options);
static::skipSpace($json, $pos);
if (is_string($string) && $length > $pos && $json[$pos] == ':') {
$pos++;
static::skipSpace($json, $pos);
$elements[$string] = static::parseValue($json, $pos, $assoc, $variables, $options);
static::skipSpace($json, $pos);
}
} while ($length > $pos && $json[$pos] == ',' && $pos++);
if ($length > $pos && $json[$pos] == '}') {
$pos++;
$result = '[';
foreach ($elements as $key => $val) {
$result .= $key . '=>' . $val . ',';
}
$result = trim($result, ',') . ']';
if (!$assoc) {
$result = '(object)' . $result . '';
}
return $result;
}
}
//function
if ($length > $pos && $json[$pos] == '(') {
$pos++;
$parameters = [];
$body = 1;
$use = $variables;
do {
if ($length > $pos && $json[$pos] == 'v' && static::isDigit($json[$pos + 1])) {
$parameter = '$v';
$pos++;
$parameter .= $json[$pos];
$pos++;
while ($length > $pos && static::isDigit($json[$pos])) {
$parameter .= $json[$pos];
$pos++;
}
$parameters[] = $parameter;
}
} while ($length > $pos && $json[$pos] == ',' && $pos++);
$variables += $parameters;
if ($length > $pos && $json[$pos] == ')') {
$pos++;
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == '=' && $json[$pos + 1] == '>') {
$pos += 2;
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == '(') {
$pos++;
static::skipSpace($json, $pos);
$body = static::parseValue($json, $pos, $assoc, $variables, $options);
static::skipSpace($json, $pos);
}
if ($length > $pos && $json[$pos] == ')') {
$pos++;
$use = implode(',', $use);
if (strlen($use) > 0) {
$use = 'use(' . $use . ')';
}
return 'function(' . implode(',', $parameters) . ')' . $use . '{return ' . $body . ';}';
}
}
}
}
//variable
if ($length > $pos && $json[$pos] == 'v' && static::isDigit($json[$pos + 1])) {
$result = '$v';
$pos++;
$result .= $json[$pos];
$pos++;
while ($length > $pos && static::isDigit($json[$pos])) {
$result .= $json[$pos];
$pos++;
}
if (!in_array($result, $variables)) {
//wrong;
$pos--;
return '';
}
if ($length > $pos && $json[$pos] == '(') {
$pos++;
$elements = [];
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == ')') {
$pos++;
$result .= '()';
return $result;
}
do {
static::skipSpace($json, $pos);
$elements[] = static::parseValue($json, $pos, $assoc, $variables, $options);
static::skipSpace($json, $pos);
} while ($length > $pos && $json[$pos] == ',' && $pos++);
static::skipSpace($json, $pos);
if ($length > $pos && $json[$pos] == ')') {
$pos++;
$result .= '(' . implode(',', $elements) . ')';
return $result;
}
return '';
}
return $result;
}
return '';
}
}