From 4d080bacd8c91e145d0f99672d12c785c3e38491 Mon Sep 17 00:00:00 2001 From: Pavel Shevaev Date: Tue, 25 Feb 2025 20:03:52 +0300 Subject: [PATCH] Better parsing error reporting --- jzon.inc.php | 55 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/jzon.inc.php b/jzon.inc.php index d9e493b..76e0a37 100644 --- a/jzon.inc.php +++ b/jzon.inc.php @@ -6,29 +6,38 @@ if(!defined('JZON_VERSION')) define('JZON_EXT_VERSION', phpversion('jzon')); } -function jzon_show_position($p, $in, $context_chars) +function jzon_show_position(int $p, string $in, int $context_lines = 5) : string { - $pre = str_replace("\n", '', jzon_get_past_input($p, $in, $context_chars)); - $c = str_repeat('-', max(0, strlen($pre) - 1)); + $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 .= '^'; - 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 ? '...' : ''); + $out[] = $arrow; + break; + } + $p = $left; + } + if(count($out) > $context_lines) + $out = array_slice($out, -$context_lines); + return implode("\n", $out); } class jzonParser { - const ERR_CONTEXT_CHARS = 200; + const ERR_CONTEXT_LINES = 5; private $in; private $len; @@ -86,9 +95,9 @@ class jzonParser 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)); + throw new Exception("Parse error: $error\n" . jzon_show_position($this->c, $this->in, self::ERR_CONTEXT_LINES)); else - throw new Exception("Parse error: $error\n" . jzon_show_position($this->len-1, $this->in, self::ERR_CONTEXT_CHARS)); + throw new Exception("Parse error: $error\n" . jzon_show_position($this->len-1, $this->in, self::ERR_CONTEXT_LINES)); } private function skip_whitespace() @@ -337,13 +346,13 @@ class jzonParser else $this->_error("'null' expected"); } - } -function jzon_parse($str) +function jzon_parse($str, ?int &$parser = null) : array { //NOTE: using super fast built-in implementation and making a gracefull fallback to a slower and // more relaxed implementation + $parser = 1; $res = json_decode($str, true); if(is_array($res)) return $res; @@ -351,13 +360,15 @@ function jzon_parse($str) //NOTE: only allowing extension implementation if versions match if(defined('JZON_EXT_VERSION') && JZON_EXT_VERSION === JZON_VERSION) { + $parser = 2; 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)); + throw new Exception($err . "\n" . jzon_show_position($err_pos, $str, jzonParser::ERR_CONTEXT_LINES)); return $res; } else { + $parser = 3; $p = new jzonParser($str); $res = $p->parse(); return $res;