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 ;
2024-03-14 14:33:15 +03:00
$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 );
2024-03-21 15:54:50 +03:00
$fval = floatval ( $str_num );
$ival = intval ( $str_num );
2022-05-16 14:19:18 +03:00
if ( $is_float )
2024-03-21 15:54:50 +03:00
$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
2024-03-21 15:54:50 +03:00
$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 );
2024-03-14 14:33:15 +03:00
$res = $p -> parse ();
return $res ;
2022-05-16 14:19:18 +03:00
}
}