JsonhPy 2.2__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.2
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "JsonhPy"
3
- version = "2.2"
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:
@@ -1263,7 +1271,7 @@ class JsonhReader:
1263
1271
  is_empty = False
1264
1272
  # Dot
1265
1273
  elif next == '.':
1266
- # Disallow dot preceding underscore
1274
+ # Disallow dot following underscore
1267
1275
  if len(number_builder.ref) >= 1 and number_builder.ref[-1] == '_':
1268
1276
  return JsonhResult.from_error("`.` must not follow `_` in number")
1269
1277
 
@@ -1437,26 +1445,40 @@ class JsonhReader:
1437
1445
  return
1438
1446
 
1439
1447
  def _read_hex_sequence(self, length: int) -> JsonhResult[int, str]:
1440
- hex_chars: str = ""
1448
+ assert(length <= 8)
1441
1449
 
1442
- for index in range(0, length):
1450
+ value: int = 0
1451
+
1452
+ for _ in range(0, length):
1443
1453
  next: str | None = self._read()
1444
1454
 
1445
1455
  # Hex digit
1446
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'))):
1447
- 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
1448
1466
  # Unexpected char
1449
1467
  else:
1450
1468
  return JsonhResult.from_error("Incorrect number of hexadecimal digits in unicode escape sequence")
1451
1469
 
1452
- # Parse unicode character from hex digits
1453
- return JsonhResult.from_value(int(hex_chars, base=16))
1470
+ # Return aggregated value
1471
+ return JsonhResult.from_value(value)
1454
1472
 
1455
- def _read_escape_sequence(self) -> JsonhResult[str, str]:
1473
+ def _read_escape_sequence(self, high_surrogate: int | None = None) -> JsonhResult[str, str]:
1456
1474
  escape_char: str | None = self._read()
1457
1475
  if escape_char == None:
1458
1476
  return JsonhResult.from_error("Expected escape sequence, got end of input")
1459
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
+
1460
1482
  match escape_char:
1461
1483
  # Reverse solidus
1462
1484
  case '\\':
@@ -1490,13 +1512,13 @@ class JsonhReader:
1490
1512
  return JsonhResult.from_value('\u001b')
1491
1513
  # Unicode hex sequence
1492
1514
  case 'u':
1493
- return self._read_hex_escape_sequence(4)
1515
+ return self._read_hex_escape_sequence(4, high_surrogate)
1494
1516
  # Short unicode hex sequence
1495
1517
  case 'x':
1496
- return self._read_hex_escape_sequence(2)
1518
+ return self._read_hex_escape_sequence(2, high_surrogate)
1497
1519
  # Long unicode hex sequence
1498
1520
  case 'U':
1499
- return self._read_hex_escape_sequence(8)
1521
+ return self._read_hex_escape_sequence(8, high_surrogate)
1500
1522
  # Escaped newline
1501
1523
  case self._NEWLINE_CHARS:
1502
1524
  # Join CR LF
@@ -1507,51 +1529,43 @@ class JsonhReader:
1507
1529
  case _:
1508
1530
  return JsonhResult.from_value(escape_char)
1509
1531
 
1510
- def _read_hex_escape_sequence(self, length: int) -> JsonhResult[str, str]:
1511
- # This method is used to combine escaped UTF-16 surrogate pairs (e.g. "\uD83D\uDC7D" -> "👽")
1512
-
1513
- # Read hex digits & convert to uint
1532
+ def _read_hex_escape_sequence(self, length: int, high_surrogate: int | None) -> JsonhResult[str, str]:
1514
1533
  code_point: JsonhResult[int, str] = self._read_hex_sequence(length)
1515
1534
  if code_point.is_error:
1516
1535
  return JsonhResult.from_error(code_point.error())
1517
1536
 
1518
- # High surrogate
1519
- if (self._is_utf16_high_surrogate(code_point.value())):
1520
- original_position: int = self.index
1521
- # Escape sequence
1522
- if self._read_one('\\'):
1523
- next: str | None = self._read_any('u', 'x', 'U')
1524
- # Low surrogate escape sequence
1525
- if next:
1526
- # Read hex sequence
1527
- low_code_point: JsonhResult[int, str]
1528
- match next:
1529
- case 'u':
1530
- low_code_point = self._read_hex_sequence(4)
1531
- case 'x':
1532
- low_code_point = self._read_hex_sequence(2)
1533
- case 'U':
1534
- low_code_point = self._read_hex_sequence(8)
1535
- # Ensure hex sequence read successfully
1536
- if low_code_point.is_error:
1537
- return JsonhResult.from_error(low_code_point.error())
1538
- # Combine high and low surrogates
1539
- code_point.value_or_none = self._utf16_surrogates_to_code_point(code_point.value(), low_code_point.value())
1540
- # Other escape sequence
1541
- else:
1542
- self.index = original_position
1543
-
1544
- # Rune
1545
- 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()))
1546
1550
 
1547
1551
  @staticmethod
1548
- def _utf16_surrogates_to_code_point(high_surrogate: int, low_surrogate: int) -> int:
1549
- 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)))
1550
1560
 
1551
1561
  @staticmethod
1552
1562
  def _is_utf16_high_surrogate(code_point: int) -> bool:
1553
1563
  return code_point >= 0xD800 and code_point <= 0xDBFF
1554
1564
 
1565
+ @staticmethod
1566
+ def _is_utf16_low_surrogate(code_point: int) -> bool:
1567
+ return code_point >= 0xDC00 and code_point <= 0xDFFF
1568
+
1555
1569
  def _peek(self) -> str | None:
1556
1570
  if self.index >= len(self.string):
1557
1571
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: JsonhPy
3
- Version: 2.2
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
File without changes
File without changes
File without changes
File without changes