JsonhPy 2.0__tar.gz → 2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JsonhPy
3
- Version: 2.0
3
+ Version: 2.3
4
4
  Summary: JSON for Humans in Python.
5
5
  Author-email: Joyless <joyless.mod@gmail.com>
6
6
  License-Expression: MIT
@@ -69,5 +69,5 @@ jsonh: str = """
69
69
  this is: awesome
70
70
  }
71
71
  """
72
- json: str = JsonhReader.parse_element_from_string(jsonh).value()
72
+ json: object = JsonhReader.parse_element_from_string(jsonh).value()
73
73
  ```
@@ -54,5 +54,5 @@ jsonh: str = """
54
54
  this is: awesome
55
55
  }
56
56
  """
57
- json: str = JsonhReader.parse_element_from_string(jsonh).value()
57
+ json: object = JsonhReader.parse_element_from_string(jsonh).value()
58
58
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "JsonhPy"
3
- version = "2.0"
3
+ version = "2.3"
4
4
  authors = [
5
5
  { name="Joyless", email="joyless.mod@gmail.com" },
6
6
  ]
@@ -1,3 +1,4 @@
1
+ import math
1
2
  from enum import Enum
2
3
  from typing import Iterator, Iterable
3
4
 
@@ -223,7 +224,15 @@ class JsonhNumberParser:
223
224
  return exponent
224
225
 
225
226
  # Multiply mantissa by 10 ^ exponent
226
- return JsonhResult.from_value(mantissa.value() * (10 ** exponent.value()))
227
+ try:
228
+ return JsonhResult.from_value(mantissa.value() * (10.0 ** exponent.value()))
229
+ except:
230
+ if mantissa.value() > 0:
231
+ return JsonhResult.from_value(math.inf)
232
+ elif mantissa.value() < 0:
233
+ return JsonhResult.from_value(-math.inf)
234
+ else:
235
+ return JsonhResult.from_value(0.0)
227
236
 
228
237
  @staticmethod
229
238
  def _parse_fractional_number(digits: str, base_digits: str) -> JsonhResult[float, str]:
@@ -238,29 +247,32 @@ class JsonhNumberParser:
238
247
 
239
248
  # Get parts of number
240
249
  whole_part: str = digits[:dot_index]
241
- fractional_part: str = digits[(dot_index + 1):]
250
+ fraction_part: str = digits[(dot_index + 1):]
242
251
 
243
252
  # Parse parts of number
244
- whole: JsonhResult[int, str] = JsonhNumberParser._parse_whole_number(whole_part, base_digits)
253
+ whole: JsonhResult[float, str] = JsonhNumberParser._parse_whole_number(whole_part, base_digits)
245
254
  if whole.is_error:
246
255
  return whole
247
- fraction: JsonhResult[int, str] = JsonhNumberParser._parse_whole_number(fractional_part, base_digits)
248
- if fraction.is_error:
249
- return fraction
250
-
251
- # Get fraction leading zeroes
252
- fraction_leading_zeroes: str = ""
253
- for index in range(0, len(fractional_part)):
254
- if fractional_part[index] == '0':
255
- fraction_leading_zeroes += '0'
256
- else:
257
- break
256
+
257
+ # Add each column of fraction digits
258
+ fraction: float = 0.0
259
+ for index in range(len(fraction_part) - 1, -1, -1):
260
+ # Get current digit
261
+ digit_char: str = fraction_part[index]
262
+ digit_int: int = base_digits.find(digit_char.lower())
263
+
264
+ # Ensure digit is valid
265
+ if digit_int < 0:
266
+ return JsonhResult.from_error(f"Invalid digit: '{digit_char}'")
267
+
268
+ # Add value of column
269
+ fraction = (fraction + digit_int) / len(base_digits)
258
270
 
259
271
  # Combine whole and fraction
260
- return JsonhResult.from_value(float(str(whole.value()) + "." + fraction_leading_zeroes + str(fraction.value())))
272
+ return JsonhResult.from_value(whole.value() + fraction)
261
273
 
262
274
  @staticmethod
263
- def _parse_whole_number(digits: str, base_digits: str) -> JsonhResult[int, str]:
275
+ def _parse_whole_number(digits: str, base_digits: str) -> JsonhResult[float, str]:
264
276
  """
265
277
  Converts a whole number (e.g. `12345`) from the given base (e.g. `01234567`) to a base-10 integer.
266
278
  """
@@ -274,7 +286,7 @@ class JsonhNumberParser:
274
286
  digits = digits[1:]
275
287
 
276
288
  # Add each column of digits
277
- integer: int = 0
289
+ integer: float = 0.0
278
290
  for index in range(0, len(digits)):
279
291
  # Get current digit
280
292
  digit_char: str = digits[index]
@@ -284,12 +296,8 @@ class JsonhNumberParser:
284
296
  if digit_int < 0:
285
297
  return JsonhResult.from_error(f"Invalid digit: '{digit_char}'")
286
298
 
287
- # Get magnitude of current digit column
288
- column_number: int = len(digits) - 1 - index
289
- column_magnitude: int = len(base_digits) ** column_number
290
-
291
299
  # Add value of column
292
- integer += digit_int * column_magnitude
300
+ integer = (integer * len(base_digits)) + digit_int
293
301
 
294
302
  # Apply sign
295
303
  if sign != 1:
@@ -541,10 +549,11 @@ class JsonhReader:
541
549
  next_element = parse_next_element()
542
550
 
543
551
  # Ensure exactly one element
544
- if self.options.parse_single_element:
545
- for token in self.read_end_of_elements():
546
- if token.is_error:
547
- return JsonhResult.from_error(token.error())
552
+ if not next_element.is_error:
553
+ if self.options.parse_single_element:
554
+ for token in self.read_end_of_elements():
555
+ if token.is_error:
556
+ return JsonhResult.from_error(token.error())
548
557
 
549
558
  return next_element
550
559
 
@@ -1262,7 +1271,7 @@ class JsonhReader:
1262
1271
  is_empty = False
1263
1272
  # Dot
1264
1273
  elif next == '.':
1265
- # Disallow dot preceding underscore
1274
+ # Disallow dot following underscore
1266
1275
  if len(number_builder.ref) >= 1 and number_builder.ref[-1] == '_':
1267
1276
  return JsonhResult.from_error("`.` must not follow `_` in number")
1268
1277
 
@@ -1436,26 +1445,40 @@ class JsonhReader:
1436
1445
  return
1437
1446
 
1438
1447
  def _read_hex_sequence(self, length: int) -> JsonhResult[int, str]:
1439
- hex_chars: str = ""
1448
+ assert(length <= 8)
1449
+
1450
+ value: int = 0
1440
1451
 
1441
- for index in range(0, length):
1452
+ for _ in range(0, length):
1442
1453
  next: str | None = self._read()
1443
1454
 
1444
1455
  # Hex digit
1445
1456
  if next != None and ((ord('0') <= ord(next) <= ord('9')) or (ord('A') <= ord(next) <= ord('F')) or (ord('a') <= ord(next) <= ord('f'))):
1446
- hex_chars += next
1457
+ # Get hex digit
1458
+ digit: int = ord(next)
1459
+ # Convert hex digit to integer
1460
+ integer: int = \
1461
+ digit - ord('A') + 10 if (digit >= ord('A') and digit <= ord('F')) else \
1462
+ digit - ord('a') + 10 if (digit >= ord('a') and digit <= ord('f')) else \
1463
+ digit - ord('0')
1464
+ # Aggregate digit into value
1465
+ value = (value * 16) + integer
1447
1466
  # Unexpected char
1448
1467
  else:
1449
1468
  return JsonhResult.from_error("Incorrect number of hexadecimal digits in unicode escape sequence")
1450
1469
 
1451
- # Parse unicode character from hex digits
1452
- return JsonhResult.from_value(int(hex_chars, base=16))
1470
+ # Return aggregated value
1471
+ return JsonhResult.from_value(value)
1453
1472
 
1454
- def _read_escape_sequence(self) -> JsonhResult[str, str]:
1473
+ def _read_escape_sequence(self, high_surrogate: int | None = None) -> JsonhResult[str, str]:
1455
1474
  escape_char: str | None = self._read()
1456
1475
  if escape_char == None:
1457
1476
  return JsonhResult.from_error("Expected escape sequence, got end of input")
1458
1477
 
1478
+ # Ensure high surrogates are completed
1479
+ if high_surrogate != None and escape_char not in ['u', 'x', 'U']:
1480
+ return JsonhResult.from_error("Expected low surrogate after high surrogate")
1481
+
1459
1482
  match escape_char:
1460
1483
  # Reverse solidus
1461
1484
  case '\\':
@@ -1489,13 +1512,13 @@ class JsonhReader:
1489
1512
  return JsonhResult.from_value('\u001b')
1490
1513
  # Unicode hex sequence
1491
1514
  case 'u':
1492
- return self._read_hex_escape_sequence(4)
1515
+ return self._read_hex_escape_sequence(4, high_surrogate)
1493
1516
  # Short unicode hex sequence
1494
1517
  case 'x':
1495
- return self._read_hex_escape_sequence(2)
1518
+ return self._read_hex_escape_sequence(2, high_surrogate)
1496
1519
  # Long unicode hex sequence
1497
1520
  case 'U':
1498
- return self._read_hex_escape_sequence(8)
1521
+ return self._read_hex_escape_sequence(8, high_surrogate)
1499
1522
  # Escaped newline
1500
1523
  case self._NEWLINE_CHARS:
1501
1524
  # Join CR LF
@@ -1506,51 +1529,43 @@ class JsonhReader:
1506
1529
  case _:
1507
1530
  return JsonhResult.from_value(escape_char)
1508
1531
 
1509
- def _read_hex_escape_sequence(self, length: int) -> JsonhResult[str, str]:
1510
- # This method is used to combine escaped UTF-16 surrogate pairs (e.g. "\uD83D\uDC7D" -> "👽")
1511
-
1512
- # Read hex digits & convert to uint
1532
+ def _read_hex_escape_sequence(self, length: int, high_surrogate: int | None) -> JsonhResult[str, str]:
1513
1533
  code_point: JsonhResult[int, str] = self._read_hex_sequence(length)
1514
1534
  if code_point.is_error:
1515
1535
  return JsonhResult.from_error(code_point.error())
1516
1536
 
1517
- # High surrogate
1518
- if (self._is_utf16_high_surrogate(code_point.value())):
1519
- original_position: int = self.index
1520
- # Escape sequence
1521
- if self._read_one('\\'):
1522
- next: str | None = self._read_any('u', 'x', 'U')
1523
- # Low surrogate escape sequence
1524
- if next:
1525
- # Read hex sequence
1526
- low_code_point: JsonhResult[int, str]
1527
- match next:
1528
- case 'u':
1529
- low_code_point = self._read_hex_sequence(4)
1530
- case 'x':
1531
- low_code_point = self._read_hex_sequence(2)
1532
- case 'U':
1533
- low_code_point = self._read_hex_sequence(8)
1534
- # Ensure hex sequence read successfully
1535
- if low_code_point.is_error:
1536
- return JsonhResult.from_error(low_code_point.error())
1537
- # Combine high and low surrogates
1538
- code_point.value_or_none = self._utf16_surrogates_to_code_point(code_point.value(), low_code_point.value())
1539
- # Other escape sequence
1540
- else:
1541
- self.index = original_position
1542
-
1543
- # Rune
1544
- return JsonhResult.from_value(chr(code_point.value()))
1537
+ # Low surrogate
1538
+ if high_surrogate != None:
1539
+ combined: JsonhResult[int, str] = self._utf16_surrogates_to_code_point(high_surrogate, code_point.value())
1540
+ if combined.is_error:
1541
+ return JsonhResult.from_error(combined.error())
1542
+ return JsonhResult.from_value(chr(combined.value()))
1543
+ else:
1544
+ # High surrogate followed by low surrogate
1545
+ if self._is_utf16_high_surrogate(code_point.value()) and self._read_one('\\'):
1546
+ return self._read_escape_sequence(code_point.value())
1547
+ # Standalone character
1548
+ else:
1549
+ return JsonhResult.from_value(chr(code_point.value()))
1545
1550
 
1546
1551
  @staticmethod
1547
- def _utf16_surrogates_to_code_point(high_surrogate: int, low_surrogate: int) -> int:
1548
- return 0x10000 + (((high_surrogate - 0xD800) << 10) | (low_surrogate - 0xDC00))
1552
+ def _utf16_surrogates_to_code_point(high_surrogate: int, low_surrogate: int) -> JsonhResult[int, str]:
1553
+ if not JsonhReader._is_utf16_high_surrogate(high_surrogate):
1554
+ return JsonhResult.from_error("High surrogate out of range")
1555
+
1556
+ if not JsonhReader._is_utf16_low_surrogate(low_surrogate):
1557
+ return JsonhResult.from_error("Low surrogate out of range")
1558
+
1559
+ return JsonhResult.from_value(0x10000 + (((high_surrogate - 0xD800) << 10) | (low_surrogate - 0xDC00)))
1549
1560
 
1550
1561
  @staticmethod
1551
1562
  def _is_utf16_high_surrogate(code_point: int) -> bool:
1552
1563
  return code_point >= 0xD800 and code_point <= 0xDBFF
1553
1564
 
1565
+ @staticmethod
1566
+ def _is_utf16_low_surrogate(code_point: int) -> bool:
1567
+ return code_point >= 0xDC00 and code_point <= 0xDFFF
1568
+
1554
1569
  def _peek(self) -> str | None:
1555
1570
  if self.index >= len(self.string):
1556
1571
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JsonhPy
3
- Version: 2.0
3
+ Version: 2.3
4
4
  Summary: JSON for Humans in Python.
5
5
  Author-email: Joyless <joyless.mod@gmail.com>
6
6
  License-Expression: MIT
@@ -69,5 +69,5 @@ jsonh: str = """
69
69
  this is: awesome
70
70
  }
71
71
  """
72
- json: str = JsonhReader.parse_element_from_string(jsonh).value()
72
+ json: object = JsonhReader.parse_element_from_string(jsonh).value()
73
73
  ```
File without changes
File without changes
File without changes