-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjson-unbounded_codecs.ads
1140 lines (894 loc) · 46.6 KB
/
json-unbounded_codecs.ads
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
------------------------------------------------------------------------------
-- --
-- JSON Parser/Constructor --
-- --
-- ------------------------------------------------------------------------ --
-- --
-- Copyright (C) 2020-2022, ANNEXI-STRAYLINE Trans-Human Ltd. --
-- All rights reserved. --
-- --
-- Original Contributors: --
-- * Richard Wai (ANNEXI-STRAYLINE) --
-- * Ensi Martini (ANNEXI-STRAYLINE) --
-- --
-- Redistribution and use in source and binary forms, with or without --
-- modification, are permitted provided that the following conditions are --
-- met: --
-- --
-- * Redistributions of source code must retain the above copyright --
-- notice, this list of conditions and the following disclaimer. --
-- --
-- * Redistributions in binary form must reproduce the above copyright --
-- notice, this list of conditions and the following disclaimer in --
-- the documentation and/or other materials provided with the --
-- distribution. --
-- --
-- * Neither the name of the copyright holder nor the names of its --
-- contributors may be used to endorse or promote products derived --
-- from this software without specific prior written permission. --
-- --
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS --
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT --
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A --
-- PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT --
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, --
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT --
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, --
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY --
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT --
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE --
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. --
-- --
------------------------------------------------------------------------------
-- This is the unbounded codec
with Ada.Iterator_Interfaces;
private with Interfaces;
private with Ada.Finalization;
private with System.Storage_Pools.Subpools;
private with JSON.Fast_Slab_Allocators;
private with JSON.Parser_Machine;
package JSON.Unbounded_Codecs is
----------------
-- JSON_Value --
----------------
-- JSON_Values represent active members of an object, or elements of an
-- array, within a Codec
type JSON_Value(<>) is tagged limited private;
function Kind (Value: JSON_Value) return JSON_Value_Kind;
function Parent_Kind (Value: JSON_Value) return JSON_Structure_Kind;
-- Returns the structure kind of the Parent structure of Value
-- (JSON_Object or JSON_Array).
--
-- Invoking Parent_Kind on the Root object raises Contraint_Error
function Name (Value: JSON_Value) return JSON_String_Value with
Pre'Class => Value.Parent_Kind = JSON_Object;
function UTF8_Name (Value: JSON_Value) return UTF_8_String with
Pre'Class => Value.Parent_Kind = JSON_Object;
-- Returns the name of Value, if it is an Object Member.
-- Constraint_Error is raised if Value is not an Object Member.
function Index (Value: JSON_Value) return Natural;
-- Returns a zero-based index of Value in its structure. This value can be
-- produced for Object Members or Array Elements, but this Index can only
-- be used to index JSON_Array JSON_Structures. In both cases, Index is
-- in the order of deserialization or appendage of the value.
function Value (Value: JSON_Value) return Boolean;
function Value (Value: JSON_Value) return JSON_Integer_Value;
function Value (Value: JSON_Value) return JSON_Float_Value;
function Value (Value: JSON_Value) return JSON_String_Value;
function UTF8_Value (Value: JSON_Value) return UTF_8_String;
-- If the Kind of Value is incompatible with the expected return type,
-- a Discriminent_Check will fail at runtime
-- Note that for structural Kinds (JSON_Object and JSON_Array), the
-- corresponding JSON_Value can be used as an index value for the
-- Unbounded_JSON_Codec
-- Value editing operations --
procedure Value (Target: in out JSON_Value;
Value : in Boolean;
Mutate: in Boolean := False)
with Pre'Class =>
(if not Mutate then Target.Kind in JSON_Boolean | JSON_Null);
procedure Value (Target: in out JSON_Value;
Value : in JSON_Integer_Value;
Mutate: in Boolean := False)
with Pre'Class =>
(if not Mutate then Target.Kind in JSON_Integer | JSON_Null);
procedure Value (Target: in out JSON_Value;
Value : in JSON_Float_Value;
Mutate: in Boolean := False)
with Pre'Class =>
(if not Mutate then Target.Kind in JSON_Float | JSON_Null);
procedure Value (Target: in out JSON_Value;
Value : in JSON_String_Value;
Mutate: in Boolean := False)
with Pre'Class =>
(if not Mutate then Target.Kind in JSON_String | JSON_Null);
-- Sets the value of Target. If Mutate is False, Constraint_Error is
-- raised if the Kind of Target does not fit the type of Value.
--
-- Otherwise, is Mutuate is True, the Target is mutated into the
-- Kind associated with the type of Value.
--
-- Null values can always be set to any type, regardless of the
-- value of Mutate.
--
-- Note: Mutating a JSON_String value to a non-string value
-- of any kind causes the dynamically allocated value to be lost,
-- and the underlying storage is not reclaimed until the codec is
-- finalized.
--
-- It is certainly pathelogical to be mutating any Values frequently
-- or many times, but in the case of JSON_String values specifically,
-- this could appear to cause a memory leak until the Codec is
-- finalized. This behaviour is a result of the performance and low
-- memory fragmentation design, and the sane assumption that these
-- sorts of mutations will not happen more than once per JSON_String
-- value.
procedure Nullify (Target: in out JSON_Value);
-- Sets Target to a 'null' value.
type JSON_Value_Reference (Ref: not null access JSON_Value) is
null record with Implicit_Dereference => Ref;
type JSON_Value_Constant_Reference
(Ref: not null access constant JSON_Value) is
null record with Implicit_Dereference => Ref;
--------------------------
-- JSON Structure Types --
--------------------------
-- JSON_Constant_Structure and JSON_Mutable_Structure represent one
-- branch of either a JSON "Object" or "Array", and thus may contain
-- further nested strucutres of either type.
--
-- Each type of JSON structure has a Length and Kind function:
-- Length: Number of Elements or Objects that are direct children of
-- the structure.
-- Kind : The kind of JSON structure represented, either a
-- JSON_Object or JSON_Array
--
-- Valid JSON structure objects can only be obtained by invoking the
-- appropriate "delve" operation of a JSON Codec.
--
-- Both flavors present the same indexing capabilities as listed below:
-- Array indexing --
subtype JSON_Array_Index is Natural;
-- Using the Javascript convention for zero-based indexing
--
-- Eg: Structure(1)
--
-- An integer index is a Javascript-style, zero-based array index. Note
-- that, internally, each object contains its own hashmap. For arrays
-- this map is indexed by hashes of each index. Array look-up is fairly
-- consistent for any index, but is slower than actual vector indexing,
-- since such JSON_Arrays are not actually represented as vectors.
--
-- However, the JSON blob is represented as a tree structure, and therefore
-- array iteration should be done via user-defined iteration, which will
-- ensure that iteration is acheived through links rather than the hash map.
--
-- If Index is out of range (> Structure.Length), or if the structure is not
-- an Array, Constraint_Error will result from an explicit check or
-- failed discriminent check.
--
-- While it might be helpful to arrange the constant and mutable json
-- structure types in a class heirachy, unfortunately the particulars of
-- user-defined indexing and iteration makes that approach
-- Object indexing --
-- Object indexing is for locating members of an object by their direct name
-- given as a JSON_String_Value. In order to prevent unnecessary exceptions
-- when attempting to index an object that does not exist in the structure,
-- the Has_Member function is provided to pre-check if a member exists in
-- the object.
--
-- Name must be a direct name for a member in the the first level
-- of Structure (which must also be an object). Paths cannot be used on
-- JSON_Structure objects. For path indexing, use the Unbounded_JSON_Codec
-- Lookup indexing operations
--
-- Has_Member returns False if Structure does not contain an element with
-- the given name. It will also return False if Structure is not a
-- JSON_Object
-- Cursor indexing --
-- A universal JSON_Structure_Cursor can be used to index into any structure
-- type, however full checking of matching Codec and structure is employed.
--
-- The Cursor must designate an element or member of the Structure, else
-- Constraint_Error will be raised.
type JSON_Structure_Cursor is limited private;
function Has_Value (Position: JSON_Structure_Cursor) return Boolean;
-- Returns True if Position is a valid (non-null) Cursor. Mostly used
-- by JSON_Structure_Iterators
package JSON_Structure_Iterators is new Ada.Iterator_Interfaces
(Cursor => JSON_Structure_Cursor,
Has_Element => Has_Value);
-- JSON_Constant_Structure --
-----------------------------
type JSON_Constant_Structure is tagged limited private with
Constant_Indexing => JCS_Constant_Reference,
Iterator_Element => JSON_Value,
Default_Iterator => JCS_Iterate;
function Kind (Structure: JSON_Constant_Structure)
return JSON_Structure_Kind;
function Length (Structure: JSON_Constant_Structure) return Natural;
function Has_Member (Structure: JSON_Constant_Structure;
Name : JSON_String_Value)
return Boolean;
function JCS_Constant_Reference
(Structure: JSON_Constant_Structure; Index: JSON_Array_Index)
return JSON_Value_Constant_Reference
with Pre => Structure.Kind = JSON_Array and Index < Structure.Length;
function JCS_Constant_Reference
(Structure: JSON_Constant_Structure; Name: JSON_String_Value)
return JSON_Value_Constant_Reference
with Pre => Structure.Kind = JSON_Object and Structure.Has_Member (Name);
function JCS_Constant_Reference (Structure: JSON_Constant_Structure;
Position : JSON_Structure_Cursor)
return JSON_Value_Constant_Reference
with Pre => Has_Value (Position);
function JCS_Iterate
(Structure: JSON_Constant_Structure)
return JSON_Structure_Iterators.Reversible_Iterator'Class;
-- JSON_Mutable_Structure --
----------------------------
type JSON_Mutable_Structure is tagged limited private with
Constant_Indexing => JMS_Constant_Reference,
Variable_Indexing => JMS_Reference,
Iterator_Element => JSON_Value,
Default_Iterator => JMS_Iterate;
function Kind (Structure: JSON_Mutable_Structure)
return JSON_Structure_Kind;
function Length (Structure: JSON_Mutable_Structure) return Natural;
function Has_Member (Structure: JSON_Mutable_Structure;
Name : JSON_String_Value)
return Boolean
with Pre'Class => Name'Length > 0;
-- Structure Building --
function Append_Null_Member (Structure: in out JSON_Mutable_Structure;
Name : in JSON_String_Value)
return JSON_Value_Reference
with Pre'Class => Structure.Kind = JSON_Object and Name'Length > 0,
Post'Class => Append_Null_Member'Result.Ref.Kind = JSON_Null;
not overriding
function Append_Null_Element (Structure: in out JSON_Mutable_Structure)
return JSON_Value_Reference
with
Pre'Class => Structure.Kind = JSON_Array,
Post'Class => Append_Null_Element'Result.Ref.Kind = JSON_Null;
-- Appends a new null value member or element to the structure, and returns
-- a reference to the new value. The new value can then be mutated normally
not overriding
function Append_Structural_Member
(Structure: in out JSON_Mutable_Structure;
Name : in JSON_String_Value;
Kind : in JSON_Structure_Kind)
return JSON_Value_Reference
with Pre'Class => Structure.Kind = JSON_Object;
not overriding
function Append_Structural_Element
(Structure: in out JSON_Mutable_Structure;
Kind : in JSON_Structure_Kind)
return JSON_Value_Reference
with Pre'Class => Structure.Kind = JSON_Array;
-- Appends a new structure of Kind to Strucure, and returns a reference.
-- The returned reference can be used to obtain a JSON_(Mutable_)Structure
-- from the Codec.
-- Indexing and Iteration --
function JMS_Constant_Reference
(Structure: JSON_Mutable_Structure; Index: JSON_Array_Index)
return JSON_Value_Constant_Reference
with Pre => Structure.Kind = JSON_Array and Index < Structure.Length;
function JMS_Reference (Structure: in out JSON_Mutable_Structure;
Index : in JSON_Array_Index)
return JSON_Value_Reference
with Pre => Structure.Kind = JSON_Array and Index < Structure.Length;
function JMS_Constant_Reference
(Structure: JSON_Mutable_Structure; Name: JSON_String_Value)
return JSON_Value_Constant_Reference
with Pre => Structure.Has_Member (Name);
function JMS_Reference (Structure: in out JSON_Mutable_Structure;
Name : in JSON_String_Value)
return JSON_Value_Reference
with Pre => Structure.Has_Member (Name);
function JMS_Constant_Reference (Structure: JSON_Mutable_Structure;
Position : JSON_Structure_Cursor)
return JSON_Value_Constant_Reference
with Pre => Has_Value (Position);
function JMS_Reference (Structure: in out JSON_Mutable_Structure;
Position : in JSON_Structure_Cursor)
return JSON_Value_Reference
with Pre => Has_Value (Position);
function JMS_Iterate
(Structure: JSON_Mutable_Structure)
return JSON_Structure_Iterators.Reversible_Iterator'Class;
--------------------------
-- Unbounded_JSON_Codec --
--------------------------
type Unbounded_JSON_Codec(<>) is tagged limited private with
Variable_Indexing => Lookup,
Constant_Indexing => Constant_Lookup;
-- Note that the indexing aspects of the codec are intended to facilitate
-- direct path-based lookup. Iteration over the entire Tree can easily be
-- acheived by iterating over the value of Root
-- Tree-wide lookup --
function Path_Exists (Codec: Unbounded_JSON_Codec;
Path : in JSON_String_Value)
return Boolean;
function Lookup (Codec: aliased in out Unbounded_JSON_Codec;
Path : in JSON_String_Value)
return JSON_Value_Reference
with Pre'Class => Codec.Path_Exists (Path);
function Constant_Lookup (Codec: aliased Unbounded_JSON_Codec;
Path : JSON_String_Value)
return JSON_Value_Constant_Reference
with Pre'Class => Codec.Path_Exists (Path);
-- Path is a dot-notation, javascript-style path to a member within some
-- hierarchy of JSON Objects in the Codec. Note that it is the
-- responsibility of the user to ensure name uniqueness, and to avoid using
-- '.' in names of members. Doing so may potentially cause unexpected
-- double-registration errors when deserializing or encoding.
--
-- If Path is not valid (no value exists), a Constraint_Error will be raised
-- due to the null exclusion of the returned reference type.
--
-- Paths CAN include array indexing ("[x]"). These follow javascript
-- conventions (zero-based indexing)
--
-- A hash table is used to accelerate path indexing, making this approach
-- extremely efficient for extracting expected items.
--
-- Examples of Lookup indexing:
--
-- declare
-- My_Codec: Unbounded_JSON_Codec := Unbounded_JSON_Codec'Input (...);
-- begin
-- if My_Codec("Country.Canada.Toronto.Temperatures[0]").Value > 20.0 then
-- ..
-- if My_Codec("My_3D_Array[12][1][0]").Kind = JSON_Null then
--
-- My_Codec("Sender_ID").Value (My_ID); -- Set the ID
function Root (Codec: aliased in out Unbounded_JSON_Codec)
return JSON_Mutable_Structure'Class;
function Constant_Root (Codec: aliased Unbounded_JSON_Codec)
return JSON_Constant_Structure'Class;
-- Returns the root structure the represents the entire deserialized
-- JSON object
--
-- If the tree is not Ready, Program_Error is raised.
function Delve (Codec : aliased in out Unbounded_JSON_Codec;
Structure: aliased in out JSON_Value'Class)
return JSON_Mutable_Structure'Class with
Pre'Class => Structure.Kind in JSON_Structure_Kind;
function Constant_Delve (Codec : aliased Unbounded_JSON_Codec;
Structure: aliased JSON_Value'Class)
return JSON_Constant_Structure'Class with
Pre'Class => Structure.Kind in JSON_Structure_Kind;
-- Delve effectivly converts a Structure JSON_Value_Reference into a
-- JSON_Structure object that can then be inspected.
--
-- Structure shall be a reference obtained from Codec, otherwise
-- Program_Error is raised.
--
-- Failing the precondition can result in a null check or a discriminent
-- check failure.
-- Construction --
function Construction_Codec (Format : JSON_Structure_Kind := JSON_Object;
Write_Only: Boolean := True)
return Unbounded_JSON_Codec;
-- Initializes a new Codec that is intended for object construction and
-- later serialization.
--
-- Format sets the Kind of the Root structure.
--
-- If Write_Only is True, paths are not registered, and path-based indexing
-- is not available. This improves performance and reduces memory usage for
-- constructions.
-- Parsing and Generation --
function Valid (Codec: Unbounded_JSON_Codec) return Boolean;
-- Returns False if the parser has discovered an invalid condition,
-- including for reasons not necessarily related to the parser (such as an
-- End_Error)
function Invalid_Because_End_Error (Codec: Unbounded_JSON_Codec)
return Boolean
with
Pre'Class => not Codec.Valid;
-- Returns True iff the inValid condition was specifically caused by the
-- raising of Ada.IO_Exceptions.End_Error, indicating the end of a stream.
--
-- The will only be useful when doing stream deserializations
function Error_Message (Codec: Unbounded_JSON_Codec) return String with
Pre'Class => not Codec.Valid;
-- Returns a messages explaining why the parser considers the input to
-- be invalid, which is prepended to a "Line:Column:" indication to
-- give an indication of where the error occured.
--
-- If the tree is Valid, Constraint_Error is raised
function Serialized_Length (Codec: in Unbounded_JSON_Codec)
return Natural;
-- Returns the number of ** UTF_8 ** characters required to serialize
-- (generate) the JSON string representation of the Root object. Note
-- that this value is generated by executing a Serialize dry-run and
-- metering the output.
procedure Serialize (Codec : in Unbounded_JSON_Codec;
Output: out UTF_8_String) with
Pre'Class => Output'Length = Codec.Serialized_Length;
-- Generates a UTF-8 encoded JSON string serialized from the tree.
-- If Output is not large enough, Constraint_Error is raised.
-- If Output is too large, the remaining space is filled with
-- whitespace.
function Serialize (Codec: in Unbounded_JSON_Codec)
return UTF_8_String with
Post'Class => Serialize'Result'Length = Codec.Serialized_Length;
-- Generates a UTF-8 encoded JSON string serialized from the tree. This
-- process is two-pass, since Serialized_Length is invoked to size the
-- returned string.
procedure Serialize
(Output_Stream: not null access Ada.Streams.Root_Stream_Type'Class;
Codec : in Unbounded_JSON_Codec);
-- Generates a UTF-8 encoded JSON string serialized from the tree, and
-- writes it to Output_Stream. Note that this is the most efficient
-- approach, as it is always single-pass.
-- -- NOTE --
-- Deserialization of a Codec never leaves the Codec in an inconsistent
-- state, even if the input is invalid. Serializing a Codec where Valid is
-- False will always produce valid JSON, however the contents of the Codec
-- will likely be incompelete.
function Deserialize (Input: UTF_8_String) return Unbounded_JSON_Codec;
-- Beware of extremely large Inputs. Input is converted into a
-- Wide_Wide_String, and if that fails (such as due to a Storage_Error),
-- the Codec will be invalidated with an "Emergency Stop", which may not
-- be a very helpful message without reading this comment.
function Deserialize
(Source: not null access Ada.Streams.Root_Stream_Type'Class)
return Unbounded_JSON_Codec;
function Deserialize
(Source: not null access Ada.Streams.Root_Stream_Type'Class;
Limits: Codec_Limits)
return Unbounded_JSON_Codec;
function Time_Bounded_Deserialize
(Source: not null access Ada.Streams.Root_Stream_Type'Class;
Budget: in Duration)
return Unbounded_JSON_Codec;
function Time_Bounded_Deserialize
(Source: not null access Ada.Streams.Root_Stream_Type'Class;
Limits: Codec_Limits;
Budget: in Duration)
return Unbounded_JSON_Codec;
-- Given a stream that shall be a UTF-8 text stream encoding a JSON
-- object, Deserialize parses the stream until a complete JSON object
-- has been deserialized.
--
-- Deserialize suppresses any exceptions experienced during the
-- deserialization process. If any such exception occurs, the Codec
-- returned, but is marked inValid.
--
-- The user should check Valid immediately after initializing a Codec
-- with Deserialize, or using 'Input.
--
-- In trule exceptional cases, if the Codec experiences exceptions during
-- initialization of the Unbounded_JSON_Codec object itself (likely
-- a Storage_Error), that exception is propegated.
--
-- This approach to error handling is intended for defensive server
-- environments where any erronious input is discarded as quickly as
-- possible, and the offending client disconnected.
--
-- For more complex error handling, particularily is client feedback
-- is needed, the user should implement their own processes via the
-- FSM_Push facilities
--
-- The Time_Bounded_Deserialize permutations implement integrated
-- "slow loris" attack protection by aborting the decode if the total
-- time spent decoding exceeds Budget before the decode completes.
--
-- The elapsed time is checked after each unicode character is decoded
-- from the Source stream.
--
-- A "slow loris" attack is when a client sends single character
-- transmissions at a rate that is (optimally) just below the connection's
-- receive timeout, thus allowing for a connection to remain active for
-- an extreme amount of time. These permuations thus protect against this
-- buy defining a hard deadline by which the decode operation must complete
procedure Disallowed_Read
(Stream: not null access Ada.Streams.Root_Stream_Type'Class;
Item : out Unbounded_JSON_Codec)
with No_Return;
-- 'Read is not supported, since Codecs are stateful, and thus would require
-- an in-out mode parameter. Invoking 'Read thus raised Program_Error.
for Unbounded_JSON_Codec'Write use Serialize;
for Unbounded_JSON_Codec'Output use Serialize;
for Unbounded_JSON_Codec'Read use Disallowed_Read;
for Unbounded_JSON_Codec'Input use Deserialize;
------------------------------
-- Unbounded_FSM_JSON_Codec --
------------------------------
type Unbounded_FSM_JSON_Codec is limited new Unbounded_JSON_Codec with
private;
-- Default initialized Unbounded_FSM_JSON_Codecs do not have valid
-- Roots. It must be initialized via repeated calls to FSM_Push as
-- described below.
not overriding
function Input_Limited_FSM (Limits: Codec_Limits)
return Unbounded_FSM_JSON_Codec;
-- Used to initalize a codec with input limits for the parser.
not overriding
procedure FSM_Push (Codec : in out Unbounded_FSM_JSON_Codec;
Next_Character: in Wide_Wide_Character;
Halt : out Boolean);
not overriding
procedure FSM_Push (Codec : in out Unbounded_FSM_JSON_Codec;
Next_Characters: in Wide_Wide_String;
Halt : out Boolean);
-- Drives the Finite State Machine parser directly with a single character
-- or sequence of characters.
--
-- FSM_Push should be invoked with new input until Halt is set to True.
--
-- The FSM will Halt for one of two reasons: Either it finished parsing
-- a complete JSON object, or it encountered invalid JSON.
--
-- If Valid is True and Halt is also True, the deserialization has completed
-- successfully.
--
-- Calling FSM_Push after Halt was signaled will have no effect on the
-- Codec, but will cause Error_Message to report encode an incorrect
-- position.
--
-- FSM_Push is not expected to raise exceptions related to the input.
--
-- These operations are exposed to allow for more direct control of
-- Deserialization streaming, particularily to prevent "slow loris" attacks.
--
-- ** FSM_Push must only be called from a Codec initailized from **
-- ** Manual_Parser, otherwise Program_Error is raised. **
--
-- Advanced Users:
-- ---------------
-- The Codec remains "consistent" during parsing, even in the event of an
-- inValid state. This means that the Codec can be queried and even modified
-- before parsing is completed, at the possible risk of exceptions. However
-- there is no danger of errnoneous execution other un-safe effects.
--
-- This could be potentialy useful for doing very early validation of
-- JSON blobs
not overriding
function Root_Initialized (Codec: Unbounded_FSM_JSON_Codec) return Boolean;
-- Returns True if the Root structure exists. This is useful for proactively
-- avoiding exceptions by querying Root before the FSM Halts. (See note for
-- "Advanced Users" above)
for Unbounded_FSM_JSON_Codec'Write use Serialize;
for Unbounded_FSM_JSON_Codec'Output use Serialize;
for Unbounded_FSM_JSON_Codec'Read use Disallowed_Read;
for Unbounded_FSM_JSON_Codec'Input use Deserialize;
-- Note that all of the deserialization operations work just as for regular
-- Unbounded_JSON_Codecs, but these operations drive the same internal FSM,
-- and will render FSM_Push useless, since the FSM state will Halted
-- immediately after deserialization in all cases, and new input will be
-- ignored.
private
--------------------------
-- Fast_Slab_Allocators --
--------------------------
-- A custom subpool-capable storage pool optimized to maximize performance,
-- and especially to minimize heap fragmentation for very high-throughput,
-- long-uptime server applications.
--
-- Each Unbounded_JSON_Codec allocates all items from its own Subpool, and
-- all deallocation occurs at finalization. Additionally, heap allocations
-- are done in constant-size (should be page-sized) allocations (slabs).
-- The Slab size can be set via the AURA configuration package with the
-- configuration value Unbounded_Codec_Slab_Size
package Fast_Slab_Allocators renames JSON.Fast_Slab_Allocators;
-- GNAT Bug workaround. See the Fast_Slab_Allocators spec for details.
Slab_Pool: Fast_Slab_Allocators.Slab_Pool_Designator
renames Fast_Slab_Allocators.Global_Slab_Pool;
subtype Subpool_Handle is System.Storage_Pools.Subpools.Subpool_Handle;
use type Subpool_Handle;
------------------
-- Slab_Strings --
------------------
-- Slab_String is a slab allocator-friendly (low fragmentation) unbounded
-- non-contigious string type
package Slab_Strings is
type Slab_String is private;
type Slab_String_Access is access Slab_String with
Storage_Pool => Slab_Pool;
-- Slab_String is kind of a super-private type. If it was general exposed
-- to the outside world, it would be limited. In this case, it is
-- non-limited to permit the mutability of JSON_Value objectes
--
-- Copying a Slab_String will not cause anything crazy to happen, except
-- that it might eventually result in a corrupted string, since two
-- "separate" strings would ultimately start overwriting eachother,
-- however it would still remain technically memory safe, since all
-- slab allocations are deallocated on dinalization of the entire Codec
--
-- Never make copies of Slab_String objects. Either use Transfer, or
-- or ensure the "original" is never used after the copy.
procedure Setup (Target : in out Slab_String;
Subpool: in not null Subpool_Handle);
-- Must be invoked prior to invoking any of the following operations.
function "=" (Left, Right: Slab_String) return Boolean;
procedure Append (Target: in out Slab_String;
Source: in JSON_String_Value);
procedure Clear (Target: in out Slab_String);
procedure Transfer (From: in out Slab_String;
To : out Slab_String);
-- After From is Transfered to To, From is Cleared. To does not need
-- to be Set-up first. To will be associated with the same Subpool as
-- From.
function Length (S: Slab_String) return Natural;
procedure To_JSON_String (Source: in Slab_String;
Target: out JSON_String_Value)
with Pre => Target'Length = Length (Source);
function To_JSON_String (Source: in Slab_String)
return JSON_String_Value;
procedure Write_Stream
(Stream: not null access Ada.Streams.Root_Stream_Type'Class;
Item : in Slab_String);
for Slab_String'Write use Write_Stream;
-- Stream Writing is intended for hashing
Chunk_Size: constant := 64;
-- The static Wide_Wide_Character'Length of each Subpool allocated
-- "chunk" (segment). The string will grow by this size on demand
private
subtype Slab_String_Chunk_Index is Natural range 0 .. Chunk_Size;
type Slab_String_Chunk;
type Slab_String_Chunk_Access is access Slab_String_Chunk with
Storage_Pool => Slab_Pool;
type Slab_String is
record
Subpool : Subpool_Handle := null;
Chain_First : Slab_String_Chunk_Access := null;
Chain_Last : Slab_String_Chunk_Access := null;
Current_Chunk: Slab_String_Chunk_Access := null;
Current_Last : Slab_String_Chunk_Index := 0;
-- Current_Last is the current index of the last character
-- of Current_Chunk, which is the last Chunk of the current
-- string. If Current_Chunk is not Chain_First, then it
-- is taken that all chunks before Current_Chunk are "full"
end record;
end Slab_Strings;
---------------------
-- JSON Structures --
---------------------
type Node is access JSON_Value with Storage_Pool => Slab_Pool;
-- We make Node a general access type so that we can assign values of
-- JSON_Value_Reference access discriminents to Node. This will always
-- be safe since the user is not able to create references to JSON_Values
-- that have no been generated via Node allocations, since JSON_Value has
-- unknown descriminents.
type JSON_Structure_Cursor is
record
Target: Node := null;
end record;
-- This is a general access type to allow us to assign reference values
-- (access descriminents pointing at a JSON_Value) to values of this type.
--
-- We know that all JSON_Values coming this way ultimately arrived via
-- allocators, since the user as no way of directly supplying
type Constant_Codec_Access is not null access constant Unbounded_JSON_Codec
with Storage_Size => 0;
type Mutable_Codec_Access is not null access all Unbounded_JSON_Codec
with Storage_Size => 0;
type JSON_Constant_Structure is tagged limited
record
Structure_Root: not null Node;
-- Always made to point to a JSON_Value with Kind of JSON_Object or
-- JSON_Array. All children of the structure are children of this
-- node.
Codec_Constant: Constant_Codec_Access;
end record;
type JSON_Mutable_Structure is tagged limited
record
Structure_Root: not null Node;
Codec_Constant: Constant_Codec_Access;
Codec_Mutable : Mutable_Codec_Access;
end record;
--------------------
-- Node_Hash_Maps --
--------------------
package Node_Hash_Maps is
type Node_Hash_Map is private;
-- Node_Hash_Map is designed to be very compact on default initialization
-- so that it can be included in all structural nodes, only taking up
-- space as needed
--
-- Similar to Slab_Strings, Node_Hash_Map would be limited if it was not
-- such a private type. Copying Node_Hash_Maps will not really cause any
-- issue, except that the Performance values will not be accurate.
--
-- Node_Hash_Maps are made non-limited to preserve internal mutability of
-- the JSON_Value objects (which are publically limited)
procedure Setup (Map : in out Node_Hash_Map;
Subpool: in not null Subpool_Handle);
-- Configures a hash map with an appropriate Subpool for allocations
procedure Register (Path_Map : in out Node_Hash_Map;
Registrant: in not null Node);
-- Use Name or Index component of Registrant (depending on the Parent),
-- and adds that registration to the appropriate map of the Parent,
-- along with the computed full-path registration to the Path_Map
-- Both Register operations also register the full path with the Path_Map
-- Raises Constraint_Error if Path/Name/Index has already been registered
function Lookup_By_Path (Path_Map: Node_Hash_Map;
Path : JSON_String_Value)
return Node;
function Lookup_By_Name (Name_Map: Node_Hash_Map;
Name : JSON_String_Value)
return Node;
function Lookup_By_Index (Index_Map: Node_Hash_Map;
Index : Natural)
return Node;
-- If the lookup fails, a null value is returned
-- Monitoring services
type Match_Level is range 1 .. 4;
Performance_Monitoring: constant Boolean := False;
type Level_Registrations is array (Match_Level) of Natural;
-- Number of registrations per match level
type Performance_Counters is
record
Primary_Registrations : Level_Registrations := (others => 0);
Overflow_Registrations: Level_Registrations := (others => 0);
-- Primary_Registrations are registrations made to the "head table"
-- Secondary_Registrations are registrations made to additional
-- tables added on demand.
--
-- Each tables is indexed with two different alternating hash-based
-- strategies. These strategies are repeated over every even and
-- odd table in the table chain
--
-- Note that invalidation does not affect these numbers (no
-- decrementation)
Total_Tables : Natural := 1;
-- Gives the total number of tables allocated for this map
Saturation_High_Water : Natural := 0;
-- Gives the numer of contigious tables from the first table
-- to be fully saturated. This value gives indications into
-- how efficient registration is for very large or complicated
-- Codecs (particularily for Path_Maps)
Full_Collisions : Natural := 0;
-- Registrations where the full hash value is an exact match,
-- but the hashed name differs. Hopefully this never goes
-- above zero.
end record;
function Performance (Map: Node_Hash_Map) return Performance_Counters;
private
-- See the package body for more detailed discussion of the
-- Node_Hash_Maps architecture
type Node_Hash_Table;
type Node_Hash_Table_Access is access Node_Hash_Table with
Storage_Pool => Slab_Pool;
type Node_Hash_Map is
record
Subpool : Subpool_Handle := null;
Table_Chain : Node_Hash_Table_Access := null;
First_Unsaturated : Node_Hash_Table_Access := null;
Unsaturated_Sequence: Positive := 1;
-- Points to the first Table that is not yet saturated. This is
-- used for registration only, to save time from needing to scan
-- saturated tables, which will tend to be towards the start.
-- The sequence is the number of the table in the chain, which
-- is used to determine if a Primary (odd) or Secondary (even)
-- strategy should be used for that table.
Expected_ID : Slab_Strings.Slab_String_Access := null;
Candidate_ID : Slab_Strings.Slab_String_Access := null;
-- These values are used for name-based lookups, wither for
-- Path_Maps and Name_Maps. These are access values
-- to allow use even if Node_Hash_Map is being accessed with
-- a constant view. These are allocated during Setup.
Profile : Performance_Counters;
end record;
end Node_Hash_Maps;
------------------------
-- JSON_Value (Nodes) --
------------------------
type Value_Container (Kind: JSON_Value_Kind := JSON_Null) is
record
case Kind is
when JSON_Object =>
Name_Map: Node_Hash_Maps.Node_Hash_Map;