$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; return substr($this->in, $start, $end - $start); } $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; } $str_num = substr($this->in, $start, $this->c - $start); if($is_float) $out = floatval($str_num); else $out = intval($str_num); } 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 if(defined('JZON_EXT_VERSION') && JZON_EXT_VERSION === JZON_VERSION) { 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); return $p->parse(); } }