-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdraft-thierry-bulk-07.xml
More file actions
3080 lines (2984 loc) · 146 KB
/
Copy pathdraft-thierry-bulk-07.xml
File metadata and controls
3080 lines (2984 loc) · 146 KB
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
<?xml version="1.0"?> <!-- -*- fill-column:100; -*- -->
<?xml-stylesheet type="text/xsl" href="lib/rfc2629.xslt"?>
<?rfc toc="yes" ?>
<?rfc symrefs="yes" ?>
<?rfc sortrefs="yes" ?>
<?rfc compact="yes"?>
<?rfc subcompact="no" ?>
<?rfc linkmailto="no" ?>
<?rfc editing="no" ?>
<?rfc comments="yes"?>
<?rfc inline="yes"?>
<?rfc rfcedstyle="yes"?>
<?rfc-ext allow-markup-in-artwork="yes" ?>
<?rfc-ext include-index="no" ?>
<rfc ipr="trust200902"
category="exp"
submissionType="IETF"
docName="draft-thierry-bulk-07"
xmlns:xi="http://www.w3.org/2001/XInclude">
<front>
<title abbrev="BULK1">Binary Universal Language Kit 1.0</title>
<author initials="P." surname="Thierry" fullname="Pierre Thierry">
<organization>Comonad Dev</organization>
<address>
<email>pierre@comonad.dev</email>
</address>
</author>
<date day="09" month="02" year="2026" />
<keyword>binary</keyword>
<abstract>
<t>
This specification describes a simple, decentrally extensible and efficient format for data
serialization.
</t>
</abstract>
</front>
<middle>
<section anchor="intro" title="Introduction">
<section title="Rationale">
<t>
This specification aims at finding an original trade-off between transparency, syntax
complexity, generality, extensibility, decentralization, discoverability, compactness,
streamability, safety, processing speed and processing footprint for a data format (see
<xref target="concepts" format="none">definitions</xref>). It is our opinion that every
widely used existing format occupy a different position than this one in the solution
space for formats, that none is better on all axes, and that this one is the current best
on several axes, hence this new design. It is also our opinion that some of those existing
formats constitute an optimal solution for their specific use case, either in a absolute
sense, or at least at the time of their design. But the ever-changing field of IT now
faces new challenges that call for a new approach.
</t>
<t>
In particular, whereas the previous trend for Internet and Web standards and programming
tools has been to create human-readable syntaxes for data and protocols, the advent of
technologies like <xref target="protobuf">protocol buffers</xref>, <xref
target="RFC8949">CBOR</xref>, <xref target="Thrift">Thrift</xref>, the various binary
serializations for JSON like <xref target="Avro">Avro</xref> or <xref
target="Smile">Smile</xref>, or the binary <xref target="RFC7540">HTTP/2</xref> seem to
indicate that the time is ripe for a generalized use of binary, reserved until now for the
low-level protocols. The lessons about flexibility learnt in the previous switch from
binary to plain text can now be applied to efficient binary syntaxes.
</t>
<section anchor="concepts" title="Definitions">
<t>
By transparency, we mean the property of a format that can be parsed even by an
application that doesn't understand the semantics of every part of the processed data.
</t>
<t>
By syntax complexity, we mean the number of different syntactic structures of the
format.
</t>
<t>
High transparency and low syntax complexity mostly have value in the face of extension,
as a fixed format doesn't need either, but even in that case, they might make the
overall format and its implementation simpler, which can have a lot of value by reducing
the risk of incompatible implementations and the likelihood that complex or opaque
elements open up security vulnerabilities.
</t>
<t>
Almost all extensible formats have a relatively high transparency and low syntax
complexity for their extensible part. The goal was thus to achieve a low syntax
complexity for the whole format, while still having high generality (i.e. that extending
the format without a new syntactic structure is as easy and practical as possible).
</t>
<t>
A good counter-example is found in most programming languages. Adding a new branching
construct cannot be done in a terse way without modifying the underlying
implementation. Such a construct either cannot be defined by user code (because of
evaluation rules) or can in a terribly verbose and inconvenient way (with lots of
boilerplate code). Notable exceptions to this limitation of programming languages are
Lisp languages (e.g. Common Lisp, Scheme or Clojure), languages with lazy evaluation
(e.g. Haskell, Purescript or Agda) and stack (or concatenative) languages (e.g. Forth,
Postscript or Factor).
</t>
<t>
On the other hand, stack languages are the canonical examples of non-transparent
formats. Each operator takes a number of operands from the stack. Not knowing the arity
of an operator makes it impossible to continue parsing, even when its evaluation was
optional to the final processing. In the design space, stack languages completely
sacrifice transparency to achieve one of the highest combination of extensibility,
compactness and speed of processing.
</t>
<t>
By generality, we mean the ability of a format to describe any type of data with a
reasonable (or better yet, high) level of compactness and simplicity. By analogy with
data structures, while both arrays and linked lists are both able to store any kind of
data, they actually do at the cost of complexity and transparency for arrays (they need
the embedding of data structure in the data or in the processing logic) and size for
linked lists (in-memory linked lists can waste as much as half or two third of the space
for the overhead of the data structure).
</t>
<t>
By extensibility, we mean the ability of a format to encode types and values that were
not anticipated when the syntax was designed.
</t>
<t>
By decentralization, we mean the ability to encode new types and values while avoiding
name collisions, but without the need of coordination. Note that the DNS, as we use it
(e.g. in domain names in module names in some programming languages, or in URIs in XML
Namespaces), is <em>not</em> decentralized in this sense, but distributed, as it cannot
work without its root servers and prior knowledge of their location.
</t>
<t>
By discoverability, we mean the ability for a processing application to automatically
discover new extensions when it encounters their use in data, with no prior knowledge of
them beforehand.
</t>
<t>
By compactness, we mean the ability of a format to encode as many diverse types of data
as possible with an overall size as small as possible.
</t>
<t>
By streamability, we mean two levels. The first, being streamed, is the ability of a
format to be transported in pieces that can be processed before the next piece is
available. The second, being broadcasted, is, when a stream of data is cut in two at an
arbitrary place, the ability of the second halt to be processed successfully.
</t>
<t>
By safety, we mean the ability for a format to have a parser implementing the full
specification while having a default behaviour that doesn't expose the system where it
runs to attacks triggered by malicious input.
</t>
<t>
By processing speed, we mean the property of a format that lends itself to benefit from
current computing architectures to be processed at high speed overall (e.g. processing
can be fast in itself, some shortcuts can be taken, or parallelization is possible).
</t>
<t>
By processing footprint, we mean the ability of a format to be processed while using a
low, possibly constrained quantity of memory.
</t>
</section>
<section title="State of the art">
<t>
Transparency, generality and extensibility are usually highly-valued traits in formats
design. Programming languages obviously feature them foremost, although their generality
usually stops at what they are supposed to express: procedures. Most of them are
ill-suited to represent arbitrary data, but notable exceptions include Lisp (where "code
is data") and Javascript, from which a subset has been extracted to exchange data, JSON,
which has seen a tremendous success for this purpose. JSON may have some caveats with
regards to generality and a relatively low compactness, but its design makes its parsing
really straightforward and fast. All of them, though, lack decentralization and
discoverability. Some of them make it possible to extend them in a distributed way if
some discipline is followed (for example, by naming modules after domain names), but the
discipline is not mandatory (and even with domain names, a change of ownership makes it
possible for name collisions).
</t>
<t>
The SGML/XML family of formats also feature good transparency, syntax complexity,
generality and extensibility and actually fare much better than programming languages on
those axes. XML namespaces also make XML naming distributed and there have been attempts
at making it compact (e.g. EXI from W3C, Fast Infoset from ISO/ITU or EBML).
</t>
<t>
All the previously cited formats clearly lack compactness, although just applying
standard compression techniques would sacrifice only very little processing time to gain
huge size reductions on most of their intended use cases, but compression may not
address their ineffectiveness at storing arbitrary bytes (and compression of the base64
encoding of arbitrary bytes can be less efficient than compression of the arbitrary
bytes).
</t>
<t>
Neither JSON nor XML are suitable for streaming and the whole document must usually be
parsed entirely before its content can be processed, which impacts processing speed and
footprint.
</t>
<t>
So-called binary formats pretty much exhibit the opposite trade-offs. Most of them have
high syntax complexity, low generality and low extensibility to achieve better
compactness. Some are specifically designed for a great generality, but many lack
extensibility. When they are extensible, it's never in a decentralized way nor are they
discoverable, both for reasons that have to do with compactness. They are usually
extremely fast to parse, and while some are designed to be streamed, few can be
broadcasted.
</t>
<t>
Actually, many binary formats are not so much formats as they are formats frameworks,
and exclude extensibility by design. For each use case, an IDL compiler creates a brand
new format that is essentially incompatible with all other formats created by the same
compiler (EBML specifically cites this property among its own disadvantages). If the IDL
compiler and framework are well designed, such a format can represent an optimum in
compactness and speed of processing, as the compiler can also automatically generate an
ad-hoc optimized parser.
</t>
<t>
Where extensibility has been planned in existing binary formats, it often doesn't get
used that much or at all because of the complications around it. Many binary formats
include reserved values meant to extend them to future uses, like the <tt>CM</tt> field
in the ZIP format. A case like this one faces an chicken-and-egg problem: if you don't
write and get a specification officially adopted, implementations might not want to
include your extension, but if your extension is purely theoretical and hasn't been
tested in the wild, you may face resistance to get it officially adopted. This is
probably why even though most compression or compressed archive formats include the
ability to later encode other compression methods, each new compression method usually
comes with its own new format.
</t>
<t>
When extensions are managed with any form of registry, another issue is that you usually
need to reserve a large set of values for free experimentation, and once an extension
gains any traction while in experimentation, its authors face the difficulty to switch
all existing implementations to the definitive values they'll get. And how experimenters
choose their temporary values makes them vulnerable to conflicts with
others. Furthermore, the process of switching between the experimental and registered
versions of the format or protocol might be error-prone and add a significant editorial
workload (<xref target="I-D.bormann-cbor-draft-numbers"/> details some of the pitfalls
and suggests a process to deal with this transition).
</t>
</section>
<section title="Use cases">
<t>
Here are some cases where the use of BULK formats and protocols would make software
engineers' and users' lives easier.
</t>
<table>
<thead><tr><th>Without BULK</th><th>With BULK</th></tr></thead>
<tbody>
<tr>
<td>
<t>
If a user has a huge collection of pictures but many of them contain extended
metadata that her image software doesn't support yet, there is no safe and easy
way for her to access it.
</t>
</td>
<td>
<t>
The user's BULK image software likely is able to show the existence of different
metadata in every image file and, with safely auto-discovered data, can
visualize the extended metadata, at least in a raw form but with human-readable
labels or, better yet, mapped into a form it knows.
</t>
</td>
</tr>
<tr>
<td>
<t>
If a user has a collection of pictures with some in a file format her image
software doesn't support yet, she cannot access the kind of common metadata that
she can expect most images to contain (like author or copyright information).
</t>
</td>
<td>
<t>
Generic BULK tools can let the user make queries about her whole image
collection, and even her broader file collection, making use of either common
metadata formats between different image or file formats, or the mapping between
different metadata formats into a single one. Queries could be for all objects,
whole files or entries inside files, that have been flagged "confidential" or
belong to some entity, or all images or videos where some person has been tagged
as visible, for example.
</t>
</td>
</tr>
<tr>
<td>
<t>
If a new image or video compression has been designed, its designers usually
need to create a whole custom container format, with custom metadata format. A
lot of work is needed for the many image or video software to be able to
accommodate reading or writing this new container, new metadata and new
codec. Because this new work involves parsing binary data, it often is a source
of security vulnerabilities.
</t>
</td>
<td>
<t>
If a new image or video compression has been designed, its designers don't need
to create anything more to embed it in BULK. If it has unusual features, it is
still pretty easy to make what is backward-compatible with existing data models
fit into the existing container structure. Even new kinds of structures leverage
existing parsing code, minimizing attack surface.
</t>
</td>
</tr>
<tr>
<td>
<t>
If a new compression, signing or encryption algorithm has been designed, and its
designers hope to see it used in existing formats, they have to work separately
for each format where it may be used and then for each software project
implementing them and in each case, they will face a chicken-and-egg problem
where software projects may be reluctant to put effort into something users may
not ultimately want or benefit from. The designers may also need to interact
with one or several registries to get their algorithm registered, which may be a
necessary upfront work. Users need to wait for each software to be updated to
include the new algorithm and sometimes suffer from incompatible implementations
at the format level (e.g. early adopters producing files with obsolete numbers
allocated to the algorithm).
</t>
</td>
<td>
<t>
If a new compression, signing or encryption algorithm has been designed, and its
designers hope to see it used in existing BULK formats, their BULK vocabulary is
guaranteed to uniquely describe the data produced by their algorithm and can be
readily embedded in any existing BULK format. The only work needed may be to
implement their algorithm as a pure function that can be plugged into the BULK
evaluation mechanism, for the relevant programming languages, so that software
projects just have to register the link between BULK vocabulary and software
plugin (or may not even need to intervene, if a safe mechanism is implemented to
auto-discover software plugins providing pure functions).
</t>
</td>
</tr>
<tr>
<td>
<t>
If a user encounters a file of an unknown type, their system might give them
useful media type identification or not, and based on that or file extension, an
advanced user might search in her system's software registry or on the Internet
and find some software that can visualize some or all of the file's content.
</t>
</td>
<td>
<t>
If a user encounters a BULK file of a yet unknown type, generic BULK tools can
safely auto-discover human-readable information about the structure of the file,
and parts of the file that the user can readily use can be easily found and
viewed or extracted.
</t>
</td>
</tr>
<tr>
<td>
<t>
When a communication protocol is designed, its designers have to choose a
trade-off between latency caused by message size, ease of debugging and
complexity of the parser. It's deceptively easy to make mistakes in the syntax
that make it harder to use or implement, but using an existing syntax ties the
protocol with this syntax' limitations.
</t>
</td>
<td>
<t>
When a BULK-based communication protocol is designed, its designers can make it
both very compact and easy to debug with very little effort. Generic tools can
present data on the wire with the support of safely auto-discovered data about
the protocol.
</t>
</td>
</tr>
</tbody>
</table>
<t>
In a few of those cases, without BULK, a registry of executable software patches or
plugins that can be automatically discovered could have been a solution, with the caveat
that almost all of our current software architectures make that possibility more
dangerous than it's worth (because of the broad ambient authority given to most code).
</t>
<t>
With BULK, there is immediately a strong incentive to provide discoverability, at first
for just data, not executable code, with safeties already in place on that process. The
data model of BULK then makes it more likely that code plugins are provided that are
designed to work with extremely limited privileges, to act as transformers of BULK
expressions.
</t>
</section>
</section>
<section title="Format overview">
<t>
A BULK stream is a stream of 8-bit bytes, in big-endian order. Parsing a BULK stream
yields a sequence of expressions, which can be either atoms or forms, which are sequences
of expressions.
</t>
<t>
Forms have a simple syntax: a starting byte marker, a sequence of expressions and an
ending byte marker.
</t>
<t>
Atoms each have a special syntax, for compactness purposes: they start with a marker byte,
followed by a static or dynamic number of bytes, depending on the type. But there are only
5 kinds: the nil atom, generic arrays, small arrays, small unsigned integers and
references.
</t>
<t>
Even booleans and floating-point numbers use this existing syntax without the need for
special cases.
</t>
<t>
References consist of a namespace marker (in almost all cases, a single byte) followed by
an identifier within this namespace (a single byte). All in all, a very little sacrifice
is made in compactness for the benefit of a very simple and resilient syntax: apart from
nil and small integers, nothing is smaller than 2 bytes, and as most forms involve a
reference followed by some content, a form is usually 4 bytes + its content.
</t>
<t>
A namespace marker in a BULK stream is associated to a namespace identified by some
identifier guaranteed to be unique without coordination (like a UUID or cryptographical
hash), thus ensuring decentralized extensibility. The stream can be processed even if the
application doesn't recognize the namespace. Parsing remains possible thanks to the
transparent syntax.
</t>
<t>
Combination of BULK namespaces, BULK streams and even other formats doesn't need any
content transformation to work. Here are some examples:
<list style="symbols">
<t>
The content of a BULK stream, enclosed in form starting and ending byte markers,
constitute a valid BULK expression. Thus BULK streams can be aggregated or annotated
within a BULK stream without modification.
</t>
<t>
A BULK format could specify in its syntax the place for a metadata expression. Whether
the specification provides its own metadata forms or not, an application could use a
BULK serialization for MARC, TEI Header, XML or RDF for this metadata expression. The
vocabulary selected would be univocally expressed by the namespace and every
vocabulary would be parsed by the same mechanisms.
</t>
<t>
Whenever a content must be stored as-is instead of serialized, or a highly-optimized
ad hoc serialization exists for some data, anything can always be stored within an
array. They can contain arbitrary bytes and there is no limit to their size.
</t>
</list>
</t>
<t>
Furthermore, BULK expressions can be evaluated. Many expressions evaluate to themselves,
but others evaluate to the result of executing a pure function, making it possible to
serialize data in an even more compact form, by eliminating boilerplate data and repeated
patterns.
</t>
</section>
<section title="Conventions and Terminology">
<t>
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as
described in <xref target="BCP14"/>.
</t>
<t>
Literal numerical values are provided in decimal or hexadecimal as appropriate.
Hexadecimal literals are prefixed with <tt>0x</tt> to distinguish them from decimal
literals.
</t>
<t>
The text notation of the BULK stream uses mnemonics for some bytes sequences. Mnemonics
are sequences of characters, excluding all capital letters and white space, like
<tt>this-is-one-mnemonic</tt> or <tt>what-the-%§!?#-is-that?</tt>. They are always
separated by white space. Outside the use of mnemonics, a sequence of bytes (of one or
more bytes) can be represented by its hexadecimal value as an unsigned integer prefixed by
<tt>0x</tt> (e.g. <tt>0x3F</tt> or <tt>0x3A0B770F</tt>). Such a sequence of bytes can
include dashes to make it more readable
(e.g. <tt>0xDDA37D36-85E6-4E6D-9B51-959E1CCE366C</tt>). Some types in this specification
define a special syntax for their representation in the text notation.
</t>
<t>
In the grammar, a shape is a pattern of bytes, following the rules of the text notation
for a BULK stream. Apart from mnemonics and fixed sequences of bytes, a shape can contain:
<list style="symbols">
<t>
an arbitrary sequence of a fixed number of bytes, represented by its size, i.e. a
number of bytes in decimal immediately followed by a B uppercase letter
(e.g. <tt>4B</tt>)
</t>
<t>
a typed sequence of bytes, represented by the name of its type, a capitalized word
(e.g. <tt>Foo</tt>); this means a sequence of bytes whose specific yield (see <xref
target="parsing" format="title"/>) has this type
</t>
<t>
a named sequence of bytes (of zero or more bytes), represented by a sequence of any
character excluding '{}' between '{' and '}' (e.g. <tt>{quux}</tt>); a named sequence
can be typed or sized, in which case it is immediately followed by ':' and a type or
size (e.g. <tt>{quux}:Bar</tt> or <tt>{quux}:12B</tt>)
</t>
</list>
</t>
<t>
The shape that describes the byte sequence of an atom is called its parsing shape. When a
shape is given for a form, it merely describes the semantics of evaluating forms of that
shape. A reference used in such a shape can be used in different shapes, with unrelated
semantics.
</t>
<t>
For example, this specification defines a way do encode a string with explicit encoding
with forms of the shape <tt>( string {enc}:Expr {string}:Expr )</tt>. But the shapes <tt>(
string {arg1}:Int {arg2}:Int )</tt> or <tt>( {arg1}:Int string {arg2}:Int )</tt> are
syntactically valid. They just evaluate to themselves as lists of three expressions, as
far as this specification is concerned.
</t>
</section>
</section>
<section title="BULK syntax">
<t>
A BULK stream is a sequence of 8-bit bytes. Bits and bytes are in big-endian order. The
result of parsing a BULK stream is a list of abstract data, called the abstract yield. BULK
parsing is injective: a BULK stream has only one abstract yield, but different BULK streams
can have the same abstract yield (if they associate namespaces to different markers, see
<xref target="nss-pkgs" format="title"/>).
</t>
<t>
A processing application is not expected to actually produce the abstract yield, but an
adaptation of the abstract yield to its own implementation, called the concrete yield. Also,
some expressions in a BULK stream may have the semantics of a transformation of the abstract
yield. A processing application MAY thus not produce or retain the concrete yield but the
result of its transformation. This specification deals mainly with the byte sequence and the
abstract yield and occasionally provides guidelines about the concrete yield. Of course, a
processing application MAY not produce any concrete yield at all but produce various data
structures and side effects from parsing the BULK stream (for example, an event sourced
application may read its event log from a BULK stream and build its application state by
applying the events, discarding each of them as soon as it has been applied).
</t>
<t>
The abstract yield is a list of expressions. Expressions can be atoms or forms. Forms are
lists of expressions. If a byte sequence is parsed as an expression, this byte sequence is
said to encode this expression.
</t>
<t>
When a sequence of bytes is named in a shape, its name can be used in this specification to
designate either the byte sequence, the expression or sequence of expressions it encodes, or
the result of evaluating those expressions. When there could be ambiguity, this
specification specifies which is designated.
</t>
<section anchor="parsing" title="Parsing algorithm">
<t>
The parser operates with a context, which is a list of expressions. Each time an
expression is parsed, it is appended at the end of the context. The initial context is the
abstract yield.
</t>
<t>
At the beginning of a BULK stream and after having consumed the byte sequence encoding a
complete expression, the parser is at the dispatch stage. At this stage, the next byte is
a marker byte, which tells the parser what kind of expression comes next (the marker byte
is the first byte of the sequence that encodes an expression). The expression appended to
the context after reading a byte sequence is called the specific yield of the byte
sequence.
</t>
<t>
The <tt>0x01</tt> and <tt>0x02</tt> marker bytes are special cases. When the parser reads
<tt>0x01</tt>, it immediately appends an empty list to the current context. This list
becomes the new context. This new context has the previous context as parent. Then the
parser returns to its dispatch stage. When the parser reads <tt>0x02</tt>, it appends
nothing to the context, but instead the parent of the current context becomes the new
context and the parser returns to the dispatch stage. Thus it is a parsing error to read
<tt>0x02</tt> when the context is the abstract yield. It is also a parsing error when the
stream ends and the parser is not at the dispatch stage in the abstract yield.
</t>
<t>
When a form contains at least one expression, the first expression is called the operator
of that form. When a form contains more than one expression, all but the first expression
are called the operands of that form.
</t>
<t>
Some forms have side-effects in their semantics. Those side-effects MUST not affect the
parsing of any expression. They can affect evaluation, in which case they MUST only affect
the evaluation of expressions in the scope of the form. The scope of an expression is the
part of its context that follows the expression. This makes BULK lexically scoped.
</t>
<t>
Whenever a parsing error is encountered, parsing of the BULK stream MUST stop.
</t>
<t>
The version of the BULK stream can affect parsing, see <xref target="version"/>.
</t>
<section title="Summary of marker bytes">
<table>
<thead><tr><th>marker</th><th>shape</th><th></th></tr></thead>
<tbody>
<tr><td><tt>00</tt></td><td><xref target="nil" format="none"><tt>nil</tt></xref></td><td></td></tr>
<tr><td><tt>01</tt></td><td><xref target="form" format="none"><tt>(</tt></xref></td><td></td></tr>
<tr><td><tt>02</tt></td><td><xref target="form" format="none"><tt>)</tt></xref></td><td></td></tr>
<tr><td><tt>03</tt></td><td><xref target="array" format="none"><tt># Nat {content}</tt></xref></td><td></td></tr>
<tr><td><tt>04–0F</tt></td><td></td><td><xref target="reserved" format="none">reserved</xref></td></tr>
<tr><td><tt>10–7F</tt></td><td><xref target="ref" format="none"><tt>Ref</tt></xref></td><td></td></tr>
<tr><td><tt>80–BF</tt></td><td><xref target="smallint" format="none"><tt>w6[value]</tt></xref></td><td></td></tr>
<tr><td><tt>C0–FF</tt></td><td><xref target="smallarray" format="none"><tt>#[size] {content}</tt></xref></td><td></td></tr>
</tbody>
</table>
</section>
<section anchor="eval" title="Evaluation">
<t>
A processing application MAY implement evaluation of BULK expressions and streams. When
evaluating a BULK stream, when the parser gets to the dispatch stage and the context is
the abstract yield (this is called a <em>dispatch point</em>), the last expression in
the context is replaced by what it evaluates to. (of course, this description is
supposed to provide the semantics of BULK evaluation, but a processing application MAY
implement evaluation with a different algorithm as long as it provides the same
semantics)
</t>
<t>
Evaluation cannot affect parsing, which means that parsing and evaluation can be done in
parallel.
</t>
<t>
The default evaluation rule is that an expression evaluates to itself. A name within a
namespace can have a value, which is what a reference associated to this name evaluates
to. A reference whose marker value is associated to no namespace or whose name has no
value evaluates to itself. How self-evaluating BULK expressions are represented in the
concrete yield is application-dependent, but future specifications MAY define a standard
API to access it, similar to the Document Object Model for XML.
</t>
<t>
The evaluation of a form obeys a special rule, though: if the operator of the form has
type <tt>Function</tt>, that function is called with an argument list and the form
evaluates to the return value if it's an atom or the evaluation of the return value if
it is a form. If the function has type <tt>LazyFunction</tt>, the argument list is the
operands of the form. If the function has type <tt>EagerFunction</tt>, the argument list
is the result of evaluating the operands of the form, from left to right. Any expression
that has type <tt>LazyFunction</tt> or <tt>EagerFunction</tt> also has type
<tt>Function</tt>.
</t>
<t>
In the abstract yield, if the first expression produced as a value by evaluation has
type <tt>EagerFunction</tt>, then after the whole abstract yield has been evaluated, the
resulting sequence of expressions MAY be evaluated as a form. This is called whole
stream evaluation, and MAY be a configuration option. In particular, a processing
application MAY choose to disable whole stream evaluation during <xref
target="streaming">streaming</xref>.
</t>
<t>
When this specification describes the evaluation of a form starting with a
<tt>LazyFunction</tt>, by default, named shapes designate the unevaluated
expressions. When this specification describes the evaluation of a form starting with an
<tt>EagerFunction</tt>, by default, named shapes designate the result of evaluating
expressions.
</t>
<t>
A form whose operand doesn't have the type <tt>Function</tt> evaluates to a form
containing the result of evaluating each expression of the form, from left to right.
</t>
<t>
When an application evaluates a BULK expression, it MUST verify that evaluation
terminates in a finite number of evaluation steps. An application MAY verify finite
termination statically or dynamically. For example, an application MAY stop evaluation
in error after a predetermined number of steps.
</t>
<t>
Whenever this specification describes the semantics of an expression, it describes the
result of evaluating that expression, either in terms of the value returned or the
side-effects executed by evaluation of the expression.
</t>
<t>
When an evaluation error is encountered, a processing application MAY not stop
processing with an error. If the processing application produces a partially evaluated
concrete yield, it MUST convey enough information for the using agent to know the order
of all expressions in the original BULK stream, which expressions were successfully
evaluated, and which were not because of evaluation errors. A processing application MAY
stop evaluation at the first evaluation error and produce the concrete yield in two
separated sections, the successfully evaluated part, followed by the unevaluated one. A
processing application MAY continue evaluation and produce the concrete yield as a
sequence of expressions, each tagged with the fact that it was successfully evaluated or
not.
</t>
<t>
The version of the BULK stream can affect evaluation, see <xref target="version"/>.
</t>
</section>
</section>
<section anchor="form" title="Forms">
<t>
<list style="hanging">
<t hangText="starting marker"><tt>0x01</tt><br/>mnemonic: <tt>(</tt></t>
<t hangText="ending marker"><tt>0x02</tt><br/>mnemonic: <tt>)</tt></t>
</list>
</t>
<section title="Difference between sequence and form">
<t>
There is a difference between a byte sequence encoding several expressions among the
current context and a byte sequence encoding a form (i.e. a single expression that is a
list of expressions). As an example, let's examine several forms of the shape <tt>( foo
{bar} )</tt>.
</t>
<t>
<list style="symbols">
<t>
In the form <tt>( foo nil nil nil )</tt>, {bar} encodes 3 expressions, and they are
three atoms in the yield.
</t>
<t>
In the form <tt>( foo nil )</tt>, {bar} is a single expression in the yield, and
that expression is an atom.
</t>
<t>
In the form <tt>( foo ( nil nil nil ) )</tt>, {bar} is also a single expression in
the yield, and that expression is a form, a list in the yield.
</t>
</list>
</t>
<t>
In a shape, when a byte sequence must yield a single expression, it has the type
<tt>Expr</tt>. So the last two examples fit the shape <tt>( foo {seq}:Expr )</tt> but
not the first.
</t>
</section>
</section>
<section title="Atoms">
<section anchor="nil" title="nil">
<t>
<list style="hanging">
<t hangText="marker"><tt>0x00</tt><br/>mnemonic: <tt>nil</tt></t>
<t hangText="parsing shape"><tt>nil</tt></t>
</list>
</t>
<t>
Apart from being a possible short marker value, the fact that the <tt>0x00</tt> byte
represents a valid atom means that a sequence of null bytes is a valid part of a BULK
stream, thus making the format less fragile. In a network communication, nil atoms can
be sent to keep the channel open. They can also be used as padding at the end of a form
or between forms.
</t>
</section>
<section title="Arrays">
<t>
Arrays can be used to store arbitrary bytes.
</t>
<t>
An array can be interpreted either as a bits sequence or as an unsigned integer in
binary notation. The choice depends on the context and the application. Actually, many
processing applications may not need make any choice, as most programming language
implementations actually also confuse unsigned integers and bits sequences to some
extent. Expressions that are unsigned integers (that is, natural numbers) have type
<tt>Nat</tt> (whether they are encoded as an array or not).
</t>
<t>
Big arrays typically store the content of a file or a binary message of another
format. They can also be used to store a vector or matrix of fixed-size elements.
</t>
<t>
In any case, the semantics of the content must be inferred by the processing
application; where ambiguity can appear, an application SHOULD enclose the array in a
form that makes the semantics explicit (e.g. <xref target="string"
format="none"><tt>string</tt></xref>, <xref target="blob"
format="none"><tt>blob</tt></xref>, or <xref target="unsigned-int"
format="none"><tt>unsigned-int</tt></xref>).
</t>
<t>
Because BULK arrays have no end markers, the payload of a BULK array can constitute the
end of the stream.
</t>
<t>
The start and end of an array are known without reading its content, which means that
its content can be skipped in constant time and mapped in memory (or read lazily by any
other means).
</t>
<t>
Because BULK can use integers with arbitrary size to store the size of an array, BULK
arrays have no limit in size.
</t>
<t>
Any array also has the type <tt>String</tt> if its contents can be decoded as a string
in the current encoding.
</t>
<t>
When this specification mentions "the bytes contained in the expression <tt>{foo}</tt>",
it never means the bytes encoding that BULK expression, but the bytes that are the
payload of that expression, which could be an array or some other byte container defined
in a BULK vocabulary.
</t>
<section anchor="array" title="Generic array">
<t>
<list style="hanging">
<t hangText="marker"><tt>0x03</tt><br/>mnemonic: <tt>#</tt></t>
<t hangText="parsing shape"><tt># Nat {content}</tt></t>
</list>
</t>
<t>
After consuming the marker byte, the parser returns to the dispatch stage. It is a
parsing error if the parsed expression is not of type <tt>Nat</tt> or if its value
cannot be recognized. This integer is not added to any context, but the parser
consumes as many bytes as this integer and they constitute the content of this array.
</t>
<t>
In the text notation, a quoted string is the notation for a generic array containing
the encoding of that string in the <xref target="stringenc" format="none">current
encoding</xref>, except if the size of the encoding is below 64 bytes, cf. <xref
target="smallarray" format="none">small arrays</xref>.
</t>
<t>
In the text notation, some text notation enclosed between balanced <tt>([</tt> and
<tt>])</tt> is the notation for a generic array containing the encoding of that text
notation, except if the size of the encoding is below 64 bytes, cf. <xref
target="smallarray" format="none">small arrays</xref>.
</t>
<t>Types: <tt>Bytes</tt>, <tt>Nat</tt></t>
</section>
<section anchor="smallarray" title="Small array">
<t>
<list style="hanging">
<t hangText="marker"><tt>0xC0–0xFF</tt><br/>mnemonic: <tt>#[size]</tt></t>
<t hangText="parsing shape"><tt>#[size] {content}</tt></t>
</list>
</t>
<t>
The 6 least significant bits of the marker byte are treated as an unsigned
integer. This integer is not added to any context, but the parser consumes as many
bytes as this integer and they constitute the content of this array.
</t>
<t>
In the text notation, the notation of the marker byte of a small array of size X is
<tt>#[X]</tt>. For example, <tt>#[2] 0x1234</tt> is a notation for the bytes
<tt>0xC2-1234</tt>.
</t>
<t>
In the text notation, a quoted string is the notation for a small array containing the
encoding of that string in the current encoding if the size of the encoding is below
64 bytes. For example, <tt>"abc"</tt> and <tt>#[3] 0x616263</tt> are the notation for
the same byte sequence if the current encoding is UTF-8.
</t>
<t>
In the text notation, some text notation enclosed between balanced <tt>([</tt> and
<tt>])</tt> is the notation for a small array containing the encoding of that text
notation if the size of the encoding is below 64 bytes. For example, <tt>([ nil 0 1
256 ])</tt> and <tt>#[6] nil w6[0] w6[1] #[2] 0x0100</tt> are the notation for the
same byte sequence.
</t>
<t>Types: <tt>Bytes</tt>, <tt>Nat</tt></t>
</section>
<section anchor="smallint" title="Small unsigned integers">
<t>
<list style="hanging">
<t hangText="marker"><tt>0x80–0xBF</tt><br/>mnemonic: <tt>w6[value]</tt></t>
<t hangText="parsing shape"><tt>w6[value]</tt></t>
</list>
</t>
<t>
The 6 least significant bits of the marker byte are the value encoded by this byte (as
bits or as an unsigned integer in binary notation).
</t>
<t>
In the text notation, the notation of the marker byte of a small unsigned integer of
value X is <tt>w6[X]</tt>. For example, <tt>w6[11]</tt> is a notation for the byte
<tt>0x8B</tt> (as is <tt>11</tt>, cf. <xref target="arithmetic"/>).
</t>
<t>Types: <tt>Bytes</tt>, <tt>Nat</tt></t>
</section>
<section anchor="nat-enc" title="Encoding natural numbers">
<t>
When the syntax of a BULK form mandates that an expression can only be a <tt>Nat</tt>,
an application SHOULD encode it as the smallest possible array using one of the
following sizes: 6, 8, 16, 32, or any multiple of 64 bits.
</t>
</section>
</section>
<section anchor="reserved" title="Reserved marker bytes">
<t>
Marker bytes <tt>0x04−0x0F</tt> are reserved for future major versions of BULK. It is a
parsing error if a BULK stream with version 1.0 contains such a marker byte (see <xref
target="version"/>).
</t>
</section>
<section anchor="ref" title="References">
<t><list style="hanging">
<t hangText="marker"><tt>0x10−0x7F</tt></t>
<t hangText="parsing shape">
<tt>{ns}:1B {name}:1B</tt>
<vspace/>
<tt>0x7F {ns'} {name}:1B</tt>
</t>
</list>
</t>
<t>
The <tt>{ns}</tt> byte is a value associated with a namespace, called the namespace
marker. Values <tt>0x10−0x13</tt> are reserved for namespaces defined by BULK
specifications. Greater values can be associated with namespaces identified by a unique
identifier.
</t>
<t>
The <tt>{name}</tt> byte is the name index within the namespace. Vocabularies with more
than 256 names thus need to be spread across several namespaces.
</t>
<t>
The specification of a namespace SHOULD include a mnemonic for the namespace and for
each defined name. When descriptions use several namespaces, the mnemonic of a reference
SHOULD be the concatenation of the namespace mnemonic, ":" and the name mnemonic if
there can be an ambiguity. For example, the <tt>fft</tt> name in namespace <tt>math</tt>
becomes <tt>math:fft</tt>.
</t>
<t>Type: <tt>Ref</tt></t>
<section title="Special case">
<t>
References have a second parsing rule. In case a BULK stream needs an important number
of namespaces, if the marker byte is <tt>0x7F</tt>, the parser continues to read bytes
until it finds a byte different than 0xFF. The sum of each of those bytes taken as
unsigned integers is the namespace marker. For example, the reference encoded by the
bytes <tt>0x7F 0xFF 0x8C 0x1A</tt> is the name 26 in the namespace associated with
522.
</t>
</section>
</section>
</section>
</section>
<section title="Kinds of namespaces">
<t>
Standard namespaces have a fixed marker value and are not identified by a unique identifier.
</t>
<t>
Extension namespaces are defined with a unique identifier, to be associated to a marker
value.
</t>
<t>
By its decentralized nature, as far as a processing application is concerned, while standard
namespaces are a special case, there is no difference between an extension namespace defined
as part of the official BULK suite and any other one.
</t>
</section>
<section title="BULK core namespace">
<t>
<list style="hanging">
<t hangText="marker"><tt>0x10</tt><br/>namespace mnemonic: <tt>bulk</tt></t>
</list>
</t>
<table>
<thead><tr><th>name</th><th>mnemonic</th><th>type</th></tr></thead>
<tbody>
<tr><td><tt>00</tt></td><td><spanx style="verb"><xref target="version"
format="none">version</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>01</tt></td><td><tt>import</tt> (<xref target="import-ns"
format="none">namespace</xref>, <xref target="import-pkg"
format="none">package</xref>)</td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>02</tt></td><td><tt>namespace</tt></td><td/></tr>
<tr><td><tt>03</tt></td><td><tt>package</tt></td><td/></tr>
<tr><td><tt>04</tt></td><td><spanx style="verb">define (<xref target="def-ns"
format="none">namespace</xref>, <xref target="def-pkg" format="none">package</xref>, <xref
target="def-name"
format="none">name</xref>)</spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>05</tt></td><td><spanx style="verb"><xref target="mnemonic"
format="none">mnemonic</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>06</tt></td><td><spanx style="verb"><xref target="explain"
format="none">explain</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>07</tt></td><td><spanx style="verb"><xref target="string"
format="none">string</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>08</tt></td><td><spanx style="verb"><xref target="iana-charset"
format="none">iana-charset</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>09</tt></td><td><spanx style="verb"><xref target="nested-bulk"
format="none">bulk</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>0A</tt></td><td><spanx style="verb"><xref target="blob"
format="none">blob</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>0B</tt></td><td><spanx style="verb"><xref target="concat"
format="none">concat</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>0C</tt></td><td><spanx style="verb"><xref target="indexed-bulk"
format="none">indexed-bulk</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>0D</tt></td><td><spanx style="verb"><xref target="indexed-array"
format="none">indexed-array</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>0E</tt></td><td><spanx style="verb"><xref target="booleans"
format="none">true</xref></spanx></td><td><tt>Boolean</tt></td></tr>
<tr><td><tt>0F</tt></td><td><spanx style="verb"><xref target="booleans"
format="none">false</xref></spanx></td><td><tt>Boolean</tt></td></tr>
<tr><td><tt>10</tt></td><td><spanx style="verb"><xref target="subst"
format="none">subst</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>11</tt></td><td><spanx style="verb"><xref target="arg"
format="none">arg</xref></spanx></td><td/></tr>
<tr><td><tt>12</tt></td><td><spanx style="verb"><xref target="rest"
format="none">rest</xref></spanx></td><td/></tr>
<tr><td><tt>13</tt></td><td><spanx style="verb"><xref target="values"
format="none">values</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>
<tr><td><tt>14</tt></td><td><spanx style="verb"><xref target="unsigned-int"
format="none">unsigned-int</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>15</tt></td><td><spanx style="verb"><xref target="signed-int"
format="none">signed-int</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>16</tt></td><td><spanx style="verb"><xref target="fraction"
format="none">fraction</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>17</tt></td><td><spanx style="verb"><xref target="binary-float"
format="none">binary-float</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>18</tt></td><td><spanx style="verb"><xref target="decimal-float"
format="none">decimal-float</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>19</tt></td><td><spanx style="verb"><xref target="binary-fixed"
format="none">binary-fixed</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>1A</tt></td><td><spanx style="verb"><xref target="decimal-fixed"
format="none">decimal-fixed</xref></spanx></td><td><tt>EagerFunction</tt></td></tr>
<tr><td><tt>1B</tt></td><td><spanx style="verb"><xref target="prefix"
format="none">prefix</xref></spanx></td><td><tt>LazyFunction</tt></td></tr>