jzon/jzon.inc.php

366 lines
8.4 KiB
PHP
Raw Permalink Normal View History

2022-05-16 14:19:18 +03:00
<?php
if(!defined('JZON_VERSION'))
{
define('JZON_VERSION', '0.0.3');
define('JZON_EXT_VERSION', phpversion('jzon'));
}
function jzon_show_position($p, $in, $context_chars)
{
$pre = str_replace("\n", '', jzon_get_past_input($p, $in, $context_chars));
$c = str_repeat('-', max(0, strlen($pre) - 1));
return $pre . str_replace("\n", '', jzon_get_upcoming_input($p, $in, $context_chars)) . "\n" . $c . "^";
}
function jzon_get_past_input($c, $in, $context_chars)
{
$past = substr($in, 0, $c+1);
return (strlen($past) > $context_chars ? '...' : '') . substr($past, -$context_chars);
}
function jzon_get_upcoming_input($c, $in, $context_chars)
{
$next = substr($in, $c+1);
return substr($next, 0, $context_chars) . (strlen($next) > $context_chars ? '...' : '');
}
class jzonParser
{
const ERR_CONTEXT_CHARS = 200;
private $in;
private $len;
private $c = 0;
static $ORD_SPACE;
static $ORD_0;
static $ORD_9;
function __construct($input)
{
$this->in = $input;
$this->c = 0;
$this->len = strlen($this->in);
self::$ORD_SPACE = ord(' ');
self::$ORD_0 = ord('0');
self::$ORD_9 = ord('9');
}
function parse()
{
$out = null;
if($this->c < $this->len && $this->in[$this->c] == '[')
$this->parse_array($out);
else
$this->parse_object($out, true);
$this->skip_whitespace();
if($this->c != $this->len)
$this->_error("Trailing content");
return $out;
}
private function parse_value(&$out)
{
$this->skip_whitespace();
$ch = $this->in[$this->c];
switch($ch)
{
case '{': $this->parse_object($out, false); break;
case '[': $this->parse_array($out); break;
case '"': $this->parse_string($out); break;
case '-': $this->parse_number($out); break;
case 'f': $this->parse_false($out); break;
case 't': $this->parse_true($out); break;
case 'n': $this->parse_null($out); break;
default:
ord($ch) >= self::$ORD_0 && ord($ch) <= self::$ORD_9 ?
$this->parse_number($out) :
$this->_error("Not expected symbol");
}
}
private function _error($error)
{
if($this->c < $this->len)
throw new Exception("Parse error: $error\n" . jzon_show_position($this->c, $this->in, self::ERR_CONTEXT_CHARS));
else
throw new Exception("Parse error: $error\n" . jzon_show_position($this->len-1, $this->in, self::ERR_CONTEXT_CHARS));
}
private function skip_whitespace()
{
//while($this->c < $this->len)
//{
while($this->c < $this->len && (ord($ch = $this->in[$this->c]) <= self::$ORD_SPACE || $ch == ','))
++$this->c;
// // skip line comment.
// if($this->c < $this->len && $this->in[$this->c] === '/' && $this->c+1 < $this->len && $this->in[$this->c+1] === '/')
// {
// ++$this->c;
// while($this->c < $this->len && $this->in[$this->c] != "\n")
// ++$this->c;
// }
// else
// break;
//}
}
private function parse_object(&$out, $root_object)
{
if(isset($this->in[$this->c]) && $this->in[$this->c] == '{')
++$this->c;
else if(!$root_object)
$this->_error("No root object");
$this->skip_whitespace();
$out = array();
// Empty object.
if(isset($this->in[$this->c]) && $this->in[$this->c] == '}')
{
++$this->c;
return;
}
while($this->c < $this->len)
{
$this->skip_whitespace();
$key = $this->parse_keyname();
$this->skip_whitespace();
if($key === null)
$this->_error("Bad key");
if($this->in[$this->c] != ':')
$this->_error("':' expected");
++$this->c;
$value = null;
$this->parse_value($value);
$out[$key] = $value;
$this->skip_whitespace();
if($this->c < $this->len && $this->in[$this->c] == '}')
{
++$this->c;
break;
}
}
return;
}
private function parse_array(&$out)
{
if($this->in[$this->c] != '[')
$this->_error("'[' expected");
++$this->c;
$this->skip_whitespace();
$out = array();
// Empty array.
if($this->c < $this->len && $this->in[$this->c] == ']')
{
++$this->c;
return;
}
while($this->c < $this->len)
{
$this->skip_whitespace();
$value = null;
$this->parse_value($value);
$out[] = $value;
$this->skip_whitespace();
if($this->c < $this->len && $this->in[$this->c] == ']')
{
++$this->c;
break;
}
}
}
private function parse_keyname()
{
if($this->in[$this->c] == '"')
{
$end_char = '"';
++$this->c;
}
else
$end_char = ':';
$start = $this->c;
while($this->c < $this->len)
{
$c = $this->in[$this->c];
if($c == $end_char)
{
$end = $this->c;
if($end_char == '"')
++$this->c;
return substr($this->in, $start, $end - $start);
}
else if(ord($c) <= self::$ORD_SPACE)
$this->_error("Found bad key character '$c' with ord=" . ord($c). " in position {$this->c}. HINT: if you ensure about file's syntax - check the file's encoding");
++$this->c;
}
return null;
}
private function parse_string(&$out)
{
$str = $this->parse_string_internal();
if($str === null)
$this->_error("Bad string");
$out = $str;
}
private function parse_string_internal()
{
if($this->in[$this->c] != '"')
return null;
//if(is_multiline_string_quotes(*input))
// return parse_multiline_string(input, allocator);
++$this->c;
$start = $this->c;
$prev = '';
while($this->c < $this->len)
{
if($this->in[$this->c] == '"' && $prev != '\\')
{
$end = $this->c;
++$this->c;
$str = substr($this->in, $start, $end - $start);
//let's remove escape slashes before "
return str_replace('\\"', '"', $str);
2022-05-16 14:19:18 +03:00
}
$prev = $this->in[$this->c];
++$this->c;
}
return null;
}
private function parse_number(&$out)
{
$is_float = false;
$start = $this->c;
if($this->in[$this->c] == '-')
++$this->c;
while($this->c < $this->len && ord($this->in[$this->c]) >= self::$ORD_0 && ord($this->in[$this->c]) <= self::$ORD_9)
++$this->c;
if($this->c < $this->len && $this->in[$this->c] == '.')
{
$is_float = true;
++$this->c;
while($this->c < $this->len && ord($this->in[$this->c]) >= self::$ORD_0 && ord($this->in[$this->c]) <= self::$ORD_9)
++$this->c;
}
if($this->c < $this->len && ($this->in[$this->c] == 'e' || $this->in[$this->c] == 'E'))
{
$is_float = true;
++$this->c;
if($this->c < $this->len && ($this->in[$this->c] == '-' || $this->in[$this->c] == '+'))
++$this->c;
while($this->c < $this->len && ord($this->in[$this->c]) >= self::$ORD_0 && ord($this->in[$this->c]) <= self::$ORD_9)
++$this->c;
}
2023-08-16 14:13:26 +03:00
$str_num = substr($this->in, $start, $this->c - $start);
$fval = floatval($str_num);
$ival = intval($str_num);
2022-05-16 14:19:18 +03:00
if($is_float)
$out = $fval;
else if(($ival < PHP_INT_MAX && $ival > PHP_INT_MIN) || strval($ival) === $str_num)
$out = $ival;
2022-05-16 14:19:18 +03:00
else
$out = $fval;
2022-05-16 14:19:18 +03:00
}
private function parse_true(&$out)
{
if(substr_compare($this->in, 'true', $this->c, 4) == 0)
{
$out = true;
$this->c += 4;
}
else
$this->_error("'true' expected");
}
private function parse_false(&$out)
{
if(substr_compare($this->in, 'false', $this->c, 5) == 0)
{
$out = false;
$this->c += 5;
}
else
$this->_error("'false' expected");
}
private function parse_null(&$out)
{
if(substr_compare($this->in, 'null', $this->c, 4) == 0)
{
$out = null;
$this->c += 4;
}
else
$this->_error("'null' expected");
}
}
function jzon_parse($str)
{
//NOTE: using super fast built-in implementation and making a gracefull fallback to a slower and
// more relaxed implementation
$res = json_decode($str, true);
if(is_array($res))
return $res;
//NOTE: only allowing extension implementation if versions match
2023-08-16 14:13:26 +03:00
if(defined('JZON_EXT_VERSION') && JZON_EXT_VERSION === JZON_VERSION)
2022-05-16 14:19:18 +03:00
{
list($ok, $err, $err_pos, $res) = jzon_parse_c($str);
if(!$ok)
throw new Exception($err . "\n" . jzon_show_position($err_pos, $str, jzonParser::ERR_CONTEXT_CHARS));
return $res;
}
else
{
$p = new jzonParser($str);
$res = $p->parse();
return $res;
2022-05-16 14:19:18 +03:00
}
}