jzon/jzon.inc.php

377 lines
8.7 KiB
PHP
Raw 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'));
}
2025-02-25 20:03:52 +03:00
function jzon_show_position(int $p, string $in, int $context_lines = 5) : string
2022-05-16 14:19:18 +03:00
{
2025-02-25 20:03:52 +03:00
$out = array();
$lines = explode("\n", $in);
//let's find out the maximum leading zeros for line numbers
$fmt_num = (int)round(log10(count($lines)));
foreach($lines as $line_idx => $line)
{
//adding line numbers and handling tabs
$out[] = sprintf('%0'.$fmt_num.'d', $line_idx) . ' ' . str_replace("\t", " ", $line);
$left = $p - (strlen($line) + 1/*taking into account \n*/);
//var_dump($left, $p, $line, $fmt_num);
if($left < 0)
{
$arrow = str_repeat('-', $fmt_num) . '-'; //line numbers
for($i=0;$i<$p;++$i)
$arrow .= ($line[$i] === "\t" ? '----' : '-');
$arrow .= '^';
2022-05-16 14:19:18 +03:00
2025-02-25 20:03:52 +03:00
$out[] = $arrow;
break;
}
$p = $left;
}
if(count($out) > $context_lines)
$out = array_slice($out, -$context_lines);
return implode("\n", $out);
2022-05-16 14:19:18 +03:00
}
class jzonParser
{
2025-02-25 20:03:52 +03:00
const ERR_CONTEXT_LINES = 5;
2022-05-16 14:19:18 +03:00
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)
2025-02-25 20:03:52 +03:00
throw new Exception("Parse error: $error\n" . jzon_show_position($this->c, $this->in, self::ERR_CONTEXT_LINES));
2022-05-16 14:19:18 +03:00
else
2025-02-25 20:03:52 +03:00
throw new Exception("Parse error: $error\n" . jzon_show_position($this->len-1, $this->in, self::ERR_CONTEXT_LINES));
2022-05-16 14:19:18 +03:00
}
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");
}
}
2025-02-25 20:03:52 +03:00
function jzon_parse($str, ?int &$parser = null) : array
2022-05-16 14:19:18 +03:00
{
//NOTE: using super fast built-in implementation and making a gracefull fallback to a slower and
// more relaxed implementation
2025-02-25 20:03:52 +03:00
$parser = 1;
2022-05-16 14:19:18 +03:00
$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
{
2025-02-25 20:03:52 +03:00
$parser = 2;
2022-05-16 14:19:18 +03:00
list($ok, $err, $err_pos, $res) = jzon_parse_c($str);
if(!$ok)
2025-02-25 20:03:52 +03:00
throw new Exception($err . "\n" . jzon_show_position($err_pos, $str, jzonParser::ERR_CONTEXT_LINES));
2022-05-16 14:19:18 +03:00
return $res;
}
else
{
2025-02-25 20:03:52 +03:00
$parser = 3;
2022-05-16 14:19:18 +03:00
$p = new jzonParser($str);
$res = $p->parse();
return $res;
2022-05-16 14:19:18 +03:00
}
}