JsonhPy 2.4__tar.gz → 2.6__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.
- {jsonhpy-2.4 → jsonhpy-2.6}/PKG-INFO +1 -1
- {jsonhpy-2.4 → jsonhpy-2.6}/pyproject.toml +1 -1
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy/JsonhPy.py +221 -200
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy.egg-info/PKG-INFO +1 -1
- {jsonhpy-2.4 → jsonhpy-2.6}/LICENSE.md +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/README.md +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/setup.cfg +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy/__init__.py +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy.egg-info/SOURCES.txt +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy.egg-info/dependency_links.txt +0 -0
- {jsonhpy-2.4 → jsonhpy-2.6}/src/JsonhPy.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import math
|
|
2
2
|
from enum import Enum
|
|
3
|
-
from typing import Iterator, Iterable
|
|
3
|
+
from typing import Iterator, Iterable, cast
|
|
4
4
|
import json
|
|
5
5
|
|
|
6
6
|
class JsonhResult[T, E]:
|
|
@@ -8,28 +8,28 @@ class JsonhResult[T, E]:
|
|
|
8
8
|
value_or_none: T | None
|
|
9
9
|
error_or_none: E | None
|
|
10
10
|
|
|
11
|
-
def __init__(self, is_error: bool, value_or_none: T | None = None, error_or_none: E | None = None):
|
|
11
|
+
def __init__(self, is_error: bool, value_or_none: T | None = None, error_or_none: E | None = None) -> None:
|
|
12
12
|
self.is_error = is_error
|
|
13
13
|
self.value_or_none = value_or_none
|
|
14
14
|
self.error_or_none = error_or_none
|
|
15
15
|
|
|
16
16
|
@staticmethod
|
|
17
|
-
def from_value[
|
|
18
|
-
return JsonhResult(False, value, None)
|
|
17
|
+
def from_value[_T, _E](value: _T = None) -> "JsonhResult[_T, _E]": # type: ignore
|
|
18
|
+
return JsonhResult[_T, _E](False, value, None)
|
|
19
19
|
|
|
20
20
|
@staticmethod
|
|
21
|
-
def from_error[
|
|
22
|
-
return JsonhResult(True, None, error)
|
|
21
|
+
def from_error[_T, _E](error: _E = None) -> "JsonhResult[_T, _E]": # type: ignore
|
|
22
|
+
return JsonhResult[_T, _E](True, None, error)
|
|
23
23
|
|
|
24
24
|
def value(self) -> T:
|
|
25
25
|
if self.is_error:
|
|
26
26
|
raise RuntimeError(f"Result was error: {self.error_or_none}")
|
|
27
|
-
return self.value_or_none
|
|
27
|
+
return self.value_or_none # type: ignore
|
|
28
28
|
|
|
29
29
|
def error(self) -> E:
|
|
30
30
|
if not self.is_error:
|
|
31
31
|
raise RuntimeError(f"Result was value: {self.value_or_none}")
|
|
32
|
-
return self.error_or_none
|
|
32
|
+
return self.error_or_none # type: ignore
|
|
33
33
|
|
|
34
34
|
def __repr__(self) -> str:
|
|
35
35
|
if self.is_error:
|
|
@@ -39,7 +39,7 @@ class JsonhResult[T, E]:
|
|
|
39
39
|
class JsonhRef[T]:
|
|
40
40
|
ref: T
|
|
41
41
|
|
|
42
|
-
def __init__(self, ref: T):
|
|
42
|
+
def __init__(self, ref: T) -> None:
|
|
43
43
|
self.ref = ref
|
|
44
44
|
|
|
45
45
|
class JsonhVersion(Enum):
|
|
@@ -135,10 +135,13 @@ class JsonTokenType(Enum):
|
|
|
135
135
|
"""
|
|
136
136
|
|
|
137
137
|
class JsonhToken:
|
|
138
|
+
"""
|
|
139
|
+
A single JSONH token.
|
|
140
|
+
"""
|
|
138
141
|
json_type: JsonTokenType
|
|
139
142
|
value: str
|
|
140
143
|
|
|
141
|
-
def __init__(self, json_type: JsonTokenType, value: str = ""):
|
|
144
|
+
def __init__(self, json_type: JsonTokenType, value: str = "") -> None:
|
|
142
145
|
self.json_type = json_type
|
|
143
146
|
self.value = value
|
|
144
147
|
|
|
@@ -185,7 +188,7 @@ class JsonhNumberParser:
|
|
|
185
188
|
|
|
186
189
|
# Apply sign
|
|
187
190
|
if sign != 1:
|
|
188
|
-
number.value_or_none *= sign
|
|
191
|
+
number.value_or_none *= sign # type: ignore
|
|
189
192
|
return number
|
|
190
193
|
|
|
191
194
|
@staticmethod
|
|
@@ -306,7 +309,7 @@ class JsonhNumberParser:
|
|
|
306
309
|
return JsonhResult.from_value(integer)
|
|
307
310
|
|
|
308
311
|
@staticmethod
|
|
309
|
-
def _index_of_any(input: str, chars: Iterable[str]) ->
|
|
312
|
+
def _index_of_any(input: str, chars: Iterable[str]) -> int:
|
|
310
313
|
for i in range(0, len(input)):
|
|
311
314
|
char: str = input[i]
|
|
312
315
|
if char in chars:
|
|
@@ -317,7 +320,7 @@ class JsonhReaderOptions:
|
|
|
317
320
|
"""
|
|
318
321
|
Options for a JsonhReader.
|
|
319
322
|
"""
|
|
320
|
-
version: JsonhVersion.LATEST
|
|
323
|
+
version: JsonhVersion = JsonhVersion.LATEST
|
|
321
324
|
"""
|
|
322
325
|
Specifies the major version of the JSONH specification to use.
|
|
323
326
|
"""
|
|
@@ -363,7 +366,7 @@ class JsonhReaderOptions:
|
|
|
363
366
|
Only some tokens can be incomplete in this mode, so it should not be relied upon.
|
|
364
367
|
"""
|
|
365
368
|
|
|
366
|
-
def __init__(self, version: JsonhVersion = JsonhVersion.LATEST, parse_single_element: bool = False, max_depth: int = 64, incomplete_inputs: bool = False):
|
|
369
|
+
def __init__(self, version: JsonhVersion = JsonhVersion.LATEST, parse_single_element: bool = False, max_depth: int = 64, incomplete_inputs: bool = False) -> None:
|
|
367
370
|
"""
|
|
368
371
|
Constructs options for a JsonhReader.
|
|
369
372
|
"""
|
|
@@ -385,11 +388,14 @@ class JsonhReaderOptions:
|
|
|
385
388
|
return options_version.value >= given_version.value
|
|
386
389
|
|
|
387
390
|
class JsonhReader:
|
|
391
|
+
"""
|
|
392
|
+
A reader that reads JSONH tokens from a `str`.
|
|
393
|
+
"""
|
|
388
394
|
string: str
|
|
389
395
|
"""
|
|
390
396
|
The string to read characters from.
|
|
391
397
|
"""
|
|
392
|
-
|
|
398
|
+
string_index: int
|
|
393
399
|
"""
|
|
394
400
|
The index in the string.
|
|
395
401
|
"""
|
|
@@ -438,7 +444,7 @@ class JsonhReader:
|
|
|
438
444
|
Constructs a reader that reads JSONH from a string.
|
|
439
445
|
"""
|
|
440
446
|
self.string = string
|
|
441
|
-
self.
|
|
447
|
+
self.string_index = 0
|
|
442
448
|
self.options = options
|
|
443
449
|
self.char_counter = 0
|
|
444
450
|
self.depth = 0
|
|
@@ -466,12 +472,12 @@ class JsonhReader:
|
|
|
466
472
|
return True
|
|
467
473
|
# Array item
|
|
468
474
|
if current_property_name == None:
|
|
469
|
-
current_array: list[object] = current_elements[-1]
|
|
475
|
+
current_array: list[object] = cast(list[object], current_elements[-1])
|
|
470
476
|
current_array.append(element)
|
|
471
477
|
return False
|
|
472
478
|
# Object property
|
|
473
479
|
else:
|
|
474
|
-
current_object: dict[str, object] = current_elements[-1]
|
|
480
|
+
current_object: dict[str, object] = cast(dict[str, object], current_elements[-1])
|
|
475
481
|
current_object[current_property_name] = element
|
|
476
482
|
current_property_name = None
|
|
477
483
|
return False
|
|
@@ -495,40 +501,40 @@ class JsonhReader:
|
|
|
495
501
|
match token_result.value().json_type:
|
|
496
502
|
# Null
|
|
497
503
|
case JsonTokenType.NULL:
|
|
498
|
-
|
|
499
|
-
if submit_element(
|
|
500
|
-
return JsonhResult.from_value(
|
|
504
|
+
element_null: None = None
|
|
505
|
+
if submit_element(element_null):
|
|
506
|
+
return JsonhResult.from_value(element_null)
|
|
501
507
|
# True
|
|
502
508
|
case JsonTokenType.TRUE:
|
|
503
|
-
|
|
504
|
-
if submit_element(
|
|
505
|
-
return JsonhResult.from_value(
|
|
509
|
+
element_true: bool = True
|
|
510
|
+
if submit_element(element_true):
|
|
511
|
+
return JsonhResult.from_value(element_true)
|
|
506
512
|
# False
|
|
507
513
|
case JsonTokenType.FALSE:
|
|
508
|
-
|
|
509
|
-
if submit_element(
|
|
510
|
-
return JsonhResult.from_value(
|
|
514
|
+
element_false: bool = False
|
|
515
|
+
if submit_element(element_false):
|
|
516
|
+
return JsonhResult.from_value(element_false)
|
|
511
517
|
# String
|
|
512
518
|
case JsonTokenType.STRING:
|
|
513
|
-
|
|
514
|
-
if submit_element(
|
|
515
|
-
return JsonhResult.from_value(
|
|
519
|
+
element_string: str = token_result.value().value
|
|
520
|
+
if submit_element(element_string):
|
|
521
|
+
return JsonhResult.from_value(element_string)
|
|
516
522
|
# Number
|
|
517
523
|
case JsonTokenType.NUMBER:
|
|
518
|
-
result: JsonhResult[float] = JsonhNumberParser.parse(token_result.value().value)
|
|
524
|
+
result: JsonhResult[float, str] = JsonhNumberParser.parse(token_result.value().value)
|
|
519
525
|
if result.is_error:
|
|
520
526
|
return JsonhResult.from_error(result.error())
|
|
521
|
-
|
|
522
|
-
if submit_element(
|
|
523
|
-
return JsonhResult.from_value(
|
|
527
|
+
element_number: float = result.value()
|
|
528
|
+
if submit_element(element_number):
|
|
529
|
+
return JsonhResult.from_value(element_number)
|
|
524
530
|
# Start Object
|
|
525
531
|
case JsonTokenType.START_OBJECT:
|
|
526
|
-
|
|
527
|
-
start_element(
|
|
532
|
+
element_object: dict[str, object] = {}
|
|
533
|
+
start_element(element_object)
|
|
528
534
|
# Start Array
|
|
529
535
|
case JsonTokenType.START_ARRAY:
|
|
530
|
-
|
|
531
|
-
start_element(
|
|
536
|
+
element_array: list[object] = []
|
|
537
|
+
start_element(element_array)
|
|
532
538
|
# End Object/Array
|
|
533
539
|
case JsonTokenType.END_OBJECT | JsonTokenType.END_ARRAY:
|
|
534
540
|
# Nested element
|
|
@@ -547,7 +553,10 @@ class JsonhReader:
|
|
|
547
553
|
case _:
|
|
548
554
|
return JsonhResult.from_error("Token type not implemented")
|
|
549
555
|
|
|
550
|
-
|
|
556
|
+
# End of input
|
|
557
|
+
return JsonhResult.from_error("Expected token, got end of input")
|
|
558
|
+
|
|
559
|
+
next_element: JsonhResult[object, str] = parse_next_element()
|
|
551
560
|
|
|
552
561
|
# Ensure exactly one element
|
|
553
562
|
if not next_element.is_error:
|
|
@@ -566,123 +575,136 @@ class JsonhReader:
|
|
|
566
575
|
|
|
567
576
|
If `indent` is not null, the output is pretty-printed with the given indentation.
|
|
568
577
|
|
|
569
|
-
The result is
|
|
578
|
+
Note: The result is **NOT** safe to embed in HTML. To safely embed in HTML, you need to escape characters like `<`, `>` and `&`.
|
|
570
579
|
"""
|
|
571
580
|
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
581
|
+
def parse_next_element_as_json() -> JsonhResult[str, str]:
|
|
582
|
+
current_depth: int = 0
|
|
583
|
+
is_start_of_structure: bool = True
|
|
584
|
+
is_property_value: bool = False
|
|
575
585
|
|
|
576
|
-
|
|
586
|
+
result_builder: str = ""
|
|
577
587
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if indent != None:
|
|
594
|
-
# Don't indent inside empty structures
|
|
595
|
-
if not (token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY] and isStartOfStructure):
|
|
596
|
-
# Don't indent comment if not included
|
|
597
|
-
if (not (token.json_type == JsonTokenType.COMMENT and (not include_comments))):
|
|
598
|
-
# Don't indent root elements
|
|
599
|
-
if current_depth > 0:
|
|
600
|
-
# Add newline before element
|
|
601
|
-
resultBuilder += '\n'
|
|
602
|
-
|
|
603
|
-
# Get current indent count
|
|
604
|
-
indent_count: int = current_depth
|
|
605
|
-
if token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY]:
|
|
606
|
-
indent_count -= 1
|
|
607
|
-
|
|
608
|
-
# Add indent
|
|
609
|
-
for _ in range(0, indent_count):
|
|
610
|
-
resultBuilder += indent
|
|
611
|
-
# Track start of structure to avoid adding leading comma
|
|
612
|
-
if token.json_type not in [JsonTokenType.NONE, JsonTokenType.COMMENT]:
|
|
613
|
-
isStartOfStructure = False
|
|
614
|
-
if token.json_type in [JsonTokenType.START_OBJECT, JsonTokenType.START_ARRAY]:
|
|
615
|
-
isStartOfStructure = True
|
|
616
|
-
|
|
617
|
-
match token.json_type:
|
|
618
|
-
# Null
|
|
619
|
-
case JsonTokenType.NONE:
|
|
620
|
-
resultBuilder += "null"
|
|
621
|
-
if current_depth == 0:
|
|
622
|
-
return JsonhResult.from_value(resultBuilder)
|
|
623
|
-
# True
|
|
624
|
-
case JsonTokenType.TRUE:
|
|
625
|
-
resultBuilder += "true"
|
|
626
|
-
if current_depth == 0:
|
|
627
|
-
return JsonhResult.from_value(resultBuilder)
|
|
628
|
-
# False
|
|
629
|
-
case JsonTokenType.FALSE:
|
|
630
|
-
resultBuilder += "false"
|
|
631
|
-
if current_depth == 0:
|
|
632
|
-
return JsonhResult.from_value(resultBuilder)
|
|
633
|
-
# String
|
|
634
|
-
case JsonTokenType.STRING:
|
|
635
|
-
resultBuilder += json.dumps(token.value, ensure_ascii=False)
|
|
636
|
-
if current_depth == 0:
|
|
637
|
-
return JsonhResult.from_value(resultBuilder)
|
|
638
|
-
# Number
|
|
639
|
-
case JsonTokenType.NUMBER:
|
|
640
|
-
result: JsonhResult[float] = JsonhNumberParser.parse(token_result.value().value)
|
|
641
|
-
if (result.is_error):
|
|
642
|
-
return JsonhResult.from_error(result.error())
|
|
643
|
-
resultBuilder += str(result.value())
|
|
644
|
-
if current_depth == 0:
|
|
645
|
-
return JsonhResult.from_value(resultBuilder)
|
|
646
|
-
# Start Object
|
|
647
|
-
case JsonTokenType.START_OBJECT:
|
|
648
|
-
resultBuilder += '{'
|
|
649
|
-
current_depth += 1
|
|
650
|
-
# Start Array
|
|
651
|
-
case JsonTokenType.START_ARRAY:
|
|
652
|
-
resultBuilder += '['
|
|
653
|
-
current_depth += 1
|
|
654
|
-
# End Object
|
|
655
|
-
case JsonTokenType.END_OBJECT:
|
|
656
|
-
resultBuilder += '}'
|
|
657
|
-
current_depth -= 1
|
|
658
|
-
if current_depth == 0:
|
|
659
|
-
return JsonhResult.from_value(resultBuilder)
|
|
660
|
-
# End Array
|
|
661
|
-
case JsonTokenType.END_ARRAY:
|
|
662
|
-
resultBuilder += ']'
|
|
663
|
-
current_depth -= 1
|
|
664
|
-
if current_depth == 0:
|
|
665
|
-
return JsonhResult.from_value(resultBuilder)
|
|
666
|
-
# Property Name
|
|
667
|
-
case JsonTokenType.PROPERTY_NAME:
|
|
668
|
-
resultBuilder += json.dumps(token.value, ensure_ascii=False)
|
|
669
|
-
resultBuilder += ':'
|
|
588
|
+
for token_result in self.read_element():
|
|
589
|
+
# Check error
|
|
590
|
+
if token_result.is_error:
|
|
591
|
+
return JsonhResult.from_error(token_result.error())
|
|
592
|
+
token: JsonhToken = token_result.value()
|
|
593
|
+
|
|
594
|
+
# Add comments and indents
|
|
595
|
+
if not is_property_value:
|
|
596
|
+
# Add comma before property/item
|
|
597
|
+
if (token.json_type not in [JsonTokenType.NONE, JsonTokenType.COMMENT]) and (current_depth > 0) and (not is_start_of_structure):
|
|
598
|
+
# Don't add trailing comma
|
|
599
|
+
if token.json_type not in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY]:
|
|
600
|
+
result_builder += ','
|
|
601
|
+
|
|
602
|
+
# Apply indentation
|
|
670
603
|
if indent != None:
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
604
|
+
# Don't indent inside empty structures
|
|
605
|
+
if not (token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY] and is_start_of_structure):
|
|
606
|
+
# Don't indent comment if not included
|
|
607
|
+
if not (token.json_type == JsonTokenType.COMMENT and (not include_comments)):
|
|
608
|
+
# Don't indent root elements
|
|
609
|
+
if current_depth > 0:
|
|
610
|
+
# Add newline before element
|
|
611
|
+
result_builder += '\n'
|
|
612
|
+
|
|
613
|
+
# Get current indent count
|
|
614
|
+
indent_count: int = current_depth
|
|
615
|
+
if token.json_type in [JsonTokenType.END_OBJECT, JsonTokenType.END_ARRAY]:
|
|
616
|
+
indent_count -= 1
|
|
617
|
+
|
|
618
|
+
# Add indent
|
|
619
|
+
for _ in range(0, indent_count):
|
|
620
|
+
result_builder += indent
|
|
621
|
+
# Track start of structure to avoid adding leading comma
|
|
622
|
+
if token.json_type not in [JsonTokenType.NONE, JsonTokenType.COMMENT]:
|
|
623
|
+
is_start_of_structure = False
|
|
624
|
+
if token.json_type in [JsonTokenType.START_OBJECT, JsonTokenType.START_ARRAY]:
|
|
625
|
+
is_start_of_structure = True
|
|
626
|
+
|
|
627
|
+
match token.json_type:
|
|
628
|
+
# Null
|
|
629
|
+
case JsonTokenType.NONE:
|
|
630
|
+
result_builder += "null"
|
|
631
|
+
if current_depth == 0:
|
|
632
|
+
return JsonhResult.from_value(result_builder)
|
|
633
|
+
# True
|
|
634
|
+
case JsonTokenType.TRUE:
|
|
635
|
+
result_builder += "true"
|
|
636
|
+
if current_depth == 0:
|
|
637
|
+
return JsonhResult.from_value(result_builder)
|
|
638
|
+
# False
|
|
639
|
+
case JsonTokenType.FALSE:
|
|
640
|
+
result_builder += "false"
|
|
641
|
+
if current_depth == 0:
|
|
642
|
+
return JsonhResult.from_value(result_builder)
|
|
643
|
+
# String
|
|
644
|
+
case JsonTokenType.STRING:
|
|
645
|
+
result_builder += json.dumps(token.value, ensure_ascii=False)
|
|
646
|
+
if current_depth == 0:
|
|
647
|
+
return JsonhResult.from_value(result_builder)
|
|
648
|
+
# Number
|
|
649
|
+
case JsonTokenType.NUMBER:
|
|
650
|
+
result: JsonhResult[float, str] = JsonhNumberParser.parse(token_result.value().value)
|
|
651
|
+
if result.is_error:
|
|
652
|
+
return JsonhResult.from_error(result.error())
|
|
653
|
+
result_builder += str(result.value()).removesuffix(".0")
|
|
654
|
+
if current_depth == 0:
|
|
655
|
+
return JsonhResult.from_value(result_builder)
|
|
656
|
+
# Start Object
|
|
657
|
+
case JsonTokenType.START_OBJECT:
|
|
658
|
+
result_builder += '{'
|
|
659
|
+
current_depth += 1
|
|
660
|
+
# Start Array
|
|
661
|
+
case JsonTokenType.START_ARRAY:
|
|
662
|
+
result_builder += '['
|
|
663
|
+
current_depth += 1
|
|
664
|
+
# End Object
|
|
665
|
+
case JsonTokenType.END_OBJECT:
|
|
666
|
+
result_builder += '}'
|
|
667
|
+
current_depth -= 1
|
|
668
|
+
if current_depth == 0:
|
|
669
|
+
return JsonhResult.from_value(result_builder)
|
|
670
|
+
# End Array
|
|
671
|
+
case JsonTokenType.END_ARRAY:
|
|
672
|
+
result_builder += ']'
|
|
673
|
+
current_depth -= 1
|
|
674
|
+
if current_depth == 0:
|
|
675
|
+
return JsonhResult.from_value(result_builder)
|
|
676
|
+
# Property Name
|
|
677
|
+
case JsonTokenType.PROPERTY_NAME:
|
|
678
|
+
result_builder += json.dumps(token.value, ensure_ascii=False)
|
|
679
|
+
result_builder += ':'
|
|
680
|
+
if indent != None:
|
|
681
|
+
result_builder += ' '
|
|
682
|
+
# Comment
|
|
683
|
+
case JsonTokenType.COMMENT:
|
|
684
|
+
if include_comments:
|
|
685
|
+
result_builder += "/*"
|
|
686
|
+
result_builder += token.value.replace("/*", "/ *").replace("*/", "* /")
|
|
687
|
+
result_builder += "*/"
|
|
688
|
+
# Not implemented
|
|
689
|
+
case _:
|
|
690
|
+
return JsonhResult.from_error("Token type not implemented")
|
|
691
|
+
|
|
692
|
+
if token.json_type != JsonTokenType.COMMENT:
|
|
693
|
+
is_property_value = token.json_type == JsonTokenType.PROPERTY_NAME
|
|
694
|
+
|
|
695
|
+
# End of input
|
|
696
|
+
return JsonhResult.from_error("Expected token, got end of input")
|
|
697
|
+
|
|
698
|
+
next_element_as_json: JsonhResult[str, str] = parse_next_element_as_json()
|
|
699
|
+
|
|
700
|
+
# Ensure exactly one element
|
|
701
|
+
if not next_element_as_json.is_error:
|
|
702
|
+
if self.options.parse_single_element:
|
|
703
|
+
for token in self.read_end_of_elements():
|
|
704
|
+
if token.is_error:
|
|
705
|
+
return JsonhResult.from_error(token.error())
|
|
706
|
+
|
|
707
|
+
return next_element_as_json
|
|
686
708
|
|
|
687
709
|
def find_property_value(self, property_name: str) -> bool:
|
|
688
710
|
"""
|
|
@@ -732,7 +754,7 @@ class JsonhReader:
|
|
|
732
754
|
# Peek char
|
|
733
755
|
return self._peek() != None
|
|
734
756
|
|
|
735
|
-
def read_end_of_elements(self) -> Iterator[JsonhResult]:
|
|
757
|
+
def read_end_of_elements(self) -> Iterator[JsonhResult[JsonhToken, str]]:
|
|
736
758
|
"""
|
|
737
759
|
Reads comments and whitespace and errors if the reader contains another element.
|
|
738
760
|
"""
|
|
@@ -827,6 +849,7 @@ class JsonhReader:
|
|
|
827
849
|
return
|
|
828
850
|
# Missing closing brace
|
|
829
851
|
yield JsonhResult.from_error("Expected `}` to end object, got end of input")
|
|
852
|
+
return
|
|
830
853
|
|
|
831
854
|
# Closing brace
|
|
832
855
|
if next == '}':
|
|
@@ -951,14 +974,12 @@ class JsonhReader:
|
|
|
951
974
|
# Optional comma
|
|
952
975
|
self._read_one(',')
|
|
953
976
|
|
|
954
|
-
def _read_property_name(self
|
|
977
|
+
def _read_property_name(self) -> Iterator[JsonhResult[JsonhToken, str]]:
|
|
955
978
|
# String
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
return
|
|
961
|
-
string = string_token.value().value
|
|
979
|
+
string_token: JsonhResult[JsonhToken, str] = self._read_string()
|
|
980
|
+
if string_token.is_error:
|
|
981
|
+
yield string_token
|
|
982
|
+
return
|
|
962
983
|
|
|
963
984
|
# Comments & whitespace
|
|
964
985
|
for token in self._read_comments_and_whitespace():
|
|
@@ -973,7 +994,7 @@ class JsonhReader:
|
|
|
973
994
|
return
|
|
974
995
|
|
|
975
996
|
# End of property name
|
|
976
|
-
yield JsonhResult.from_value(JsonhToken(JsonTokenType.PROPERTY_NAME,
|
|
997
|
+
yield JsonhResult.from_value(JsonhToken(JsonTokenType.PROPERTY_NAME, string_token.value().value))
|
|
977
998
|
|
|
978
999
|
def _read_array(self) -> Iterator[JsonhResult[JsonhToken, str]]:
|
|
979
1000
|
# Opening bracket
|
|
@@ -1058,7 +1079,7 @@ class JsonhReader:
|
|
|
1058
1079
|
return self._read_quoteless_string("", is_verbatim)
|
|
1059
1080
|
|
|
1060
1081
|
# Count multiple quotes
|
|
1061
|
-
start_quote_counter = 1
|
|
1082
|
+
start_quote_counter: int = 1
|
|
1062
1083
|
while self._read_one(start_quote):
|
|
1063
1084
|
start_quote_counter += 1
|
|
1064
1085
|
|
|
@@ -1073,24 +1094,24 @@ class JsonhReader:
|
|
|
1073
1094
|
string_builder: str = ""
|
|
1074
1095
|
|
|
1075
1096
|
while True:
|
|
1076
|
-
|
|
1077
|
-
if
|
|
1097
|
+
next2: str | None = self._read()
|
|
1098
|
+
if next2 == None:
|
|
1078
1099
|
return JsonhResult.from_error("Expected end of string, got end of input")
|
|
1079
1100
|
|
|
1080
1101
|
# Partial end quote was actually part of string
|
|
1081
|
-
if
|
|
1102
|
+
if next2 != start_quote:
|
|
1082
1103
|
string_builder += start_quote * end_quote_counter
|
|
1083
1104
|
end_quote_counter = 0
|
|
1084
1105
|
|
|
1085
1106
|
# End quote
|
|
1086
|
-
if
|
|
1107
|
+
if next2 == start_quote:
|
|
1087
1108
|
end_quote_counter += 1
|
|
1088
1109
|
if end_quote_counter == start_quote_counter:
|
|
1089
1110
|
break
|
|
1090
1111
|
# Escape sequence
|
|
1091
|
-
elif
|
|
1112
|
+
elif next2 == '\\':
|
|
1092
1113
|
if is_verbatim:
|
|
1093
|
-
string_builder +=
|
|
1114
|
+
string_builder += next2
|
|
1094
1115
|
else:
|
|
1095
1116
|
escape_sequence_result: JsonhResult[str, str] = self._read_escape_sequence()
|
|
1096
1117
|
if escape_sequence_result.is_error:
|
|
@@ -1098,7 +1119,7 @@ class JsonhReader:
|
|
|
1098
1119
|
string_builder += escape_sequence_result.value()
|
|
1099
1120
|
# Literal character
|
|
1100
1121
|
else:
|
|
1101
|
-
string_builder +=
|
|
1122
|
+
string_builder += next2
|
|
1102
1123
|
|
|
1103
1124
|
# Condition: skip remaining steps unless started with multiple quotes
|
|
1104
1125
|
if start_quote_counter > 1:
|
|
@@ -1107,19 +1128,19 @@ class JsonhReader:
|
|
|
1107
1128
|
leading_whitespace_newline_counter: int = 0
|
|
1108
1129
|
index: int = 0
|
|
1109
1130
|
while index < len(string_builder):
|
|
1110
|
-
|
|
1131
|
+
next3: str = string_builder[index]
|
|
1111
1132
|
|
|
1112
1133
|
# Newline
|
|
1113
|
-
if
|
|
1134
|
+
if next3 in self._NEWLINE_CHARS:
|
|
1114
1135
|
# Join CR LF
|
|
1115
|
-
if
|
|
1136
|
+
if next3 == '\r' and index + 1 < len(string_builder) and string_builder[index + 1] == '\n':
|
|
1116
1137
|
index += 1
|
|
1117
1138
|
|
|
1118
1139
|
has_leading_whitespace_newline = True
|
|
1119
1140
|
leading_whitespace_newline_counter = index + 1
|
|
1120
1141
|
break
|
|
1121
1142
|
# Non-whitespace
|
|
1122
|
-
elif
|
|
1143
|
+
elif next3 not in self._WHITESPACE_CHARS:
|
|
1123
1144
|
break
|
|
1124
1145
|
|
|
1125
1146
|
index += 1
|
|
@@ -1132,19 +1153,19 @@ class JsonhReader:
|
|
|
1132
1153
|
trailing_whitespace_counter: int = 0
|
|
1133
1154
|
index: int = 0
|
|
1134
1155
|
while index < len(string_builder):
|
|
1135
|
-
|
|
1156
|
+
next4: str = string_builder[index]
|
|
1136
1157
|
|
|
1137
1158
|
# Newline
|
|
1138
|
-
if
|
|
1159
|
+
if next4 in self._NEWLINE_CHARS:
|
|
1139
1160
|
has_trailing_newline_whitespace = True
|
|
1140
1161
|
last_newline_index = index
|
|
1141
1162
|
trailing_whitespace_counter = 0
|
|
1142
1163
|
|
|
1143
1164
|
# Join CR LF
|
|
1144
|
-
if
|
|
1165
|
+
if next4 == '\r' and index + 1 < len(string_builder) and string_builder[index + 1] == '\n':
|
|
1145
1166
|
index += 1
|
|
1146
1167
|
# Whitespace
|
|
1147
|
-
elif
|
|
1168
|
+
elif next4 in self._WHITESPACE_CHARS:
|
|
1148
1169
|
trailing_whitespace_counter += 1
|
|
1149
1170
|
# Non-whitespace
|
|
1150
1171
|
else:
|
|
@@ -1168,14 +1189,14 @@ class JsonhReader:
|
|
|
1168
1189
|
line_leading_whitespace_counter: int = 0
|
|
1169
1190
|
index: int = 0
|
|
1170
1191
|
while index < len(string_builder):
|
|
1171
|
-
|
|
1192
|
+
next5: str = string_builder[index]
|
|
1172
1193
|
|
|
1173
1194
|
# Newline
|
|
1174
|
-
if
|
|
1195
|
+
if next5 in self._NEWLINE_CHARS:
|
|
1175
1196
|
is_line_leading_whitespace = True
|
|
1176
1197
|
line_leading_whitespace_counter = 0
|
|
1177
1198
|
# Whitespace
|
|
1178
|
-
elif
|
|
1199
|
+
elif next5 in self._WHITESPACE_CHARS:
|
|
1179
1200
|
if is_line_leading_whitespace:
|
|
1180
1201
|
# Increment line-leading whitespace
|
|
1181
1202
|
line_leading_whitespace_counter += 1
|
|
@@ -1219,7 +1240,7 @@ class JsonhReader:
|
|
|
1219
1240
|
if is_verbatim:
|
|
1220
1241
|
string_builder += next
|
|
1221
1242
|
else:
|
|
1222
|
-
escape_sequence_result: JsonhResult[str] = self._read_escape_sequence()
|
|
1243
|
+
escape_sequence_result: JsonhResult[str, str] = self._read_escape_sequence()
|
|
1223
1244
|
if escape_sequence_result.is_error:
|
|
1224
1245
|
return JsonhResult.from_error(escape_sequence_result.error())
|
|
1225
1246
|
string_builder += escape_sequence_result.value()
|
|
@@ -1325,9 +1346,9 @@ class JsonhReader:
|
|
|
1325
1346
|
has_leading_zero = False
|
|
1326
1347
|
|
|
1327
1348
|
# Read main number
|
|
1328
|
-
main_result: JsonhResult[None,
|
|
1349
|
+
main_result: JsonhResult[None, str] = self._read_number_no_exponent(number_builder, base_digits, has_base_specifier, has_leading_zero)
|
|
1329
1350
|
if main_result.is_error:
|
|
1330
|
-
number: JsonhResult[
|
|
1351
|
+
number: JsonhResult[JsonhToken, str] = JsonhResult.from_error(main_result.error())
|
|
1331
1352
|
partial_chars_read: str = number_builder.ref
|
|
1332
1353
|
return number, partial_chars_read
|
|
1333
1354
|
|
|
@@ -1340,16 +1361,16 @@ class JsonhReader:
|
|
|
1340
1361
|
|
|
1341
1362
|
# Missing digit between base specifier and exponent (e.g. `0xe+`)
|
|
1342
1363
|
if has_base_specifier and len(number_builder.ref) == 4:
|
|
1343
|
-
|
|
1364
|
+
number2: JsonhResult[JsonhToken, str] = JsonhResult.from_error("Missing digit between base specifier and exponent")
|
|
1344
1365
|
partial_chars_read: str = number_builder.ref
|
|
1345
|
-
return
|
|
1366
|
+
return number2, partial_chars_read
|
|
1346
1367
|
|
|
1347
1368
|
# Read exponent number
|
|
1348
|
-
exponent_result: JsonhResult[None,
|
|
1369
|
+
exponent_result: JsonhResult[None, str] = self._read_number_no_exponent(number_builder, base_digits)
|
|
1349
1370
|
if exponent_result.is_error:
|
|
1350
|
-
|
|
1371
|
+
number3: JsonhResult[JsonhToken, str] = JsonhResult.from_error(exponent_result.error())
|
|
1351
1372
|
partial_chars_read: str = number_builder.ref
|
|
1352
|
-
return
|
|
1373
|
+
return number3, partial_chars_read
|
|
1353
1374
|
# Exponent
|
|
1354
1375
|
else:
|
|
1355
1376
|
exponent_char: str | None = self._read_any('e', 'E')
|
|
@@ -1362,20 +1383,20 @@ class JsonhReader:
|
|
|
1362
1383
|
number_builder.ref += exponent_sign
|
|
1363
1384
|
|
|
1364
1385
|
# Read exponent number
|
|
1365
|
-
|
|
1366
|
-
if
|
|
1367
|
-
|
|
1386
|
+
exponent_result2: JsonhResult[None, str] = self._read_number_no_exponent(number_builder, base_digits)
|
|
1387
|
+
if exponent_result2.is_error:
|
|
1388
|
+
number4: JsonhResult[JsonhToken, str] = JsonhResult.from_error(exponent_result2.error())
|
|
1368
1389
|
partial_chars_read: str = number_builder.ref
|
|
1369
|
-
return
|
|
1390
|
+
return number4, partial_chars_read
|
|
1370
1391
|
|
|
1371
1392
|
# End of number
|
|
1372
|
-
|
|
1393
|
+
number5: JsonhResult[JsonhToken, str] = JsonhResult.from_value(JsonhToken(JsonTokenType.NUMBER, number_builder.ref))
|
|
1373
1394
|
partial_chars_read: str = ""
|
|
1374
|
-
return
|
|
1395
|
+
return number5, partial_chars_read
|
|
1375
1396
|
|
|
1376
|
-
def _read_number_no_exponent(self, number_builder: JsonhRef[str], base_digits: str, has_base_specifier: bool = False, has_leading_zero: bool = False) -> JsonhResult[None,
|
|
1397
|
+
def _read_number_no_exponent(self, number_builder: JsonhRef[str], base_digits: str, has_base_specifier: bool = False, has_leading_zero: bool = False) -> JsonhResult[None, str]:
|
|
1377
1398
|
# Leading underscore
|
|
1378
|
-
if (not has_base_specifier) and not
|
|
1399
|
+
if (not has_base_specifier) and (not has_leading_zero) and self._peek() == '_':
|
|
1379
1400
|
return JsonhResult.from_error("Leading `_` in number")
|
|
1380
1401
|
|
|
1381
1402
|
is_fraction: bool = False
|
|
@@ -1694,16 +1715,16 @@ class JsonhReader:
|
|
|
1694
1715
|
return code_point >= 0xDC00 and code_point <= 0xDFFF
|
|
1695
1716
|
|
|
1696
1717
|
def _peek(self) -> str | None:
|
|
1697
|
-
if self.
|
|
1718
|
+
if self.string_index >= len(self.string):
|
|
1698
1719
|
return None
|
|
1699
|
-
next: str = self.string[self.
|
|
1720
|
+
next: str = self.string[self.string_index]
|
|
1700
1721
|
return next
|
|
1701
1722
|
|
|
1702
1723
|
def _read(self) -> str | None:
|
|
1703
|
-
if self.
|
|
1724
|
+
if self.string_index >= len(self.string):
|
|
1704
1725
|
return None
|
|
1705
|
-
next: str = self.string[self.
|
|
1706
|
-
self.
|
|
1726
|
+
next: str = self.string[self.string_index]
|
|
1727
|
+
self.string_index += 1
|
|
1707
1728
|
self.char_counter += 1
|
|
1708
1729
|
return next
|
|
1709
1730
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|