@tabnas/proto 0.6.6 → 0.6.8

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.
@@ -0,0 +1,1065 @@
1
+ /* Copyright (c) 2026 Richard Rodger and other contributors, MIT License */
2
+
3
+ // Generated by ../embed-translate.js. Edit tabnas.plugin.json or alchemy/*,
4
+ // then run npm run embed.
5
+
6
+ type TranslationPart = Readonly<{
7
+ entry: string
8
+ source?: string
9
+ }>
10
+
11
+ type TranslationParts = Readonly<{
12
+ manifest: string
13
+ lift?: TranslationPart
14
+ embed?: TranslationPart
15
+ render?: TranslationPart
16
+ }>
17
+
18
+ const TRANSLATION: TranslationParts = Object.freeze({
19
+ manifest: `{
20
+ "$schema": "https://tabnas.dev/schema/plugin.schema.json",
21
+ "name": "@tabnas/proto",
22
+ "go": "github.com/tabnas/proto/go",
23
+ "rust": "tabnas-proto",
24
+ "description": "Protocol Buffers IDL parsing for the tabnas engine (proto2, proto3, editions).",
25
+ "engine": "@tabnas/parser",
26
+ "extensions": [
27
+ ".proto"
28
+ ],
29
+ "languageId": "proto",
30
+ "specDir": "test/spec",
31
+ "clib": {
32
+ "dir": "go/clib",
33
+ "library": "libtabnasproto",
34
+ "abi": "v1",
35
+ "errorCodes": [
36
+ "usage",
37
+ "grammar",
38
+ "handle",
39
+ "internal"
40
+ ]
41
+ },
42
+ "errorCodes": [],
43
+ "docs": "https://tabnas.dev/docs/packages",
44
+ "repository": "https://github.com/tabnas/proto",
45
+ "versionSource": "ts/package.json",
46
+ "translate": {
47
+ "reads": "tree",
48
+ "writes": "tree",
49
+ "root": "object",
50
+ "schema": "proto-descriptor",
51
+ "render": "alchemy/render.alc",
52
+ "loss": [
53
+ "The file is written anew from its descriptor: comments, blank lines and white space are not kept, and each statement is written on lines of its own, indented two spaces a level.",
54
+ "Statements are written in the order the descriptor's lists keep them, so statements of different kinds that the file interleaved are regrouped: the syntax or edition first, then the package, the imports and the file's options, then the messages, the enums, the services and the extensions, and in a message its options, its fields with each nested message where it keeps its place among them, its enums, its extensions and its ranges.",
55
+ "A file that declares no syntax is written with syntax = \\"proto2\\", the version the reader gives it, and a descriptor's name, which the reader never sets, is not written.",
56
+ "A label the descriptor does not keep is not written as the file spelled it: an optional field of a proto2 file is written optional whether or not the file said so, and an optional field of a proto3 file that the descriptor does not mark proto3Optional, or of an edition file, is written with no label.",
57
+ "A map field is written as map<K, V> from the entry message the reader made for it, a group as a group block from its nested message, and a proto3 optional field's synthetic oneof is not written, since the reader makes each of them again.",
58
+ "A number is written as the descriptor holds it: a hexadecimal or octal field number, enum value or range bound is written in decimal, an exclusive range end as its last number or max, and a number the reader read as NaN (a digit separator, 1_0, which the lexer takes and JavaScript's Number refuses) as 0_0, which reads back as NaN.",
59
+ "A string literal is written between double quotes, or single quotes where its text holds a double quote and no single quote, its escapes as written, since the descriptor keeps a literal's text between its quotes; a text that holds both quotes is written between double quotes, and reads back as written only where each double quote in it is escaped.",
60
+ "An option's value is one string to the descriptor whether the file wrote it as an identifier, a string literal or an aggregate in braces, so it is written as a string literal, as an aggregate only where no literal can hold its text (a line break, or both quotes), and as an identifier for the options descriptor.proto types as enums (optimize_for, ctype, jstype, retention, targets, idempotency_level and the features) when its text is made of identifier characters; protoc reads an enum-valued or message-valued option written as a literal as a string, and a quoted value of such an option that begins with a digit, which protoc refuses, does not read back.",
61
+ "A default is written bare for a field that is not a string or bytes when its text is made of a constant's characters, and as a string literal otherwise; a quoted default of such a field whose text is made of those characters but spells no one constant, which protoc refuses, does not read back.",
62
+ "Reserved names are written as string literals in a proto2 or proto3 file and as identifiers in an edition file, a message's or an enum's reserved ranges in one reserved statement and its names in another, each extension range in an extensions statement of its own with its options, and each extension in an extend block of its own."
63
+ ]
64
+ }
65
+ }
66
+ `,
67
+ render: Object.freeze({ entry: "proto-render", source: `; Protocol Buffers' render: the reader's tree, a FileDescriptorProto, written
68
+ ; back as one \`.proto\` file. This is a library of definitions, each named
69
+ ; \`proto-...\`, which a host links with its own program (alchemy's
70
+ ; \`compile_sources\`); its entry point is \`proto-render\`, and nothing else
71
+ ; here is the host's to call.
72
+ ;
73
+ ; The render writes the reader's own tree and no other, as the
74
+ ; translation design says a render of a format whose value has a shape of
75
+ ; its own does: the manifest names that shape as the schema
76
+ ; \`proto-descriptor\`, and a host composes into this render only from a
77
+ ; source of the same schema or from a program that builds the tree. The
78
+ ; contract is the round trip: the text it writes reads back, through this
79
+ ; repository's reader, as the tree it was given. The tree keeps what a
80
+ ; descriptor holds and not how the file spelled it, so the render chooses
81
+ ; a spelling for each part, the one that reads back the same, as the
82
+ ; manifest's loss list says:
83
+ ;
84
+ ; - The file is the \`syntax\` or \`edition\` declaration, then the package,
85
+ ; the imports (\`public\`, \`weak\`, and edition 2024's \`import option\`) and
86
+ ; the file's options, in the order the tree holds them, then the
87
+ ; messages, the enums, the services and the extensions, each a statement
88
+ ; or a block on lines of its own, indented two spaces a level.
89
+ ; - A message's body is its options, then its fields in order, each nested
90
+ ; message written where it keeps its place among the fields' own (a map
91
+ ; field's entry and a group's message are nested types too, written as
92
+ ; the map field and the group), the declared oneofs as blocks around
93
+ ; their fields (a proto3 \`optional\` field's synthetic oneof is not one:
94
+ ; the field is written \`optional\`), then its enums, its extensions (an
95
+ ; \`extend\` block each), its extension ranges and its reserved ranges and
96
+ ; names, in the order the tree holds them. A map field's entry is a
97
+ ; nested type the reader would make for the field again: named for it
98
+ ; by protoc's rule and like the reader's entry in every member. Any
99
+ ; other, a message the file declared with the option \`mapEntry\` among
100
+ ; them, is written as a message, and the field as a field of its type.
101
+ ; - A field is its label (none in a oneof; \`optional\` for a proto2 field
102
+ ; the tree marks optional, and for a proto3 field the tree marks
103
+ ; \`proto3Optional\`; nothing for the other optional fields), its type (a
104
+ ; scalar's keyword, or the type name as written), its name, its number
105
+ ; and its options in brackets: \`default\`, \`json_name\` and the rest.
106
+ ; - An exclusive range end (a message's reserved and extension ranges) is
107
+ ; written one below the tree's, or \`max\` where it is the bound \`to max\`
108
+ ; gives (536870912, or 2147483647 under \`message_set_wire_format\`); an
109
+ ; enum's inclusive end as it is, \`max\` for 2147483647.
110
+ ; - A string from a string literal is kept as its text between the quotes,
111
+ ; escapes as written, so it is written between double quotes, or single
112
+ ; quotes where it holds a double quote and no single quote. An option's
113
+ ; value the reader read as an identifier, a quoted literal or an
114
+ ; aggregate in braces is one string to the tree: it is written quoted,
115
+ ; as an aggregate (\`{...}\`) where no literal can hold it (a line break, or
116
+ ; both quotes), and as an identifier for the options descriptor.proto
117
+ ; types as enums (\`optimize_for\`, \`ctype\`, \`jstype\`, \`retention\`,
118
+ ; \`targets\`, \`idempotency_level\` and the features) where it is made of
119
+ ; identifier characters. A default is written bare for a field that is
120
+ ; not a string or bytes, where it is made of the characters a constant
121
+ ; is, and as a literal otherwise. alchemy cannot read a string a
122
+ ; character at a time, so three texts a file can hold, each refused by
123
+ ; protoc but one, do not read back: a literal's text holding both
124
+ ; quotes with a double quote unescaped, a quoted value of an enum-typed
125
+ ; option that begins with a digit, and a quoted default of a field that
126
+ ; is not a string whose text is a constant's characters spelling no one
127
+ ; constant.
128
+ ; - A field or enum number the reader could not read (JavaScript's Number
129
+ ; of \`1_0\`, the lexer's digit separator) is NaN, null in JSON, and is
130
+ ; written \`0_0\`, which reads back the same; an option's number that is
131
+ ; null in JSON is infinite, and is written \`1e999\`.
132
+ ;
133
+ ; Anything else fails with TARGET_VALUE_UNREPRESENTABLE, naming what was
134
+ ; met: a root that is not an object, a syntax or an edition a \`.proto\`
135
+ ; file cannot declare, a member a descriptor holds as a string that is not
136
+ ; one, a field type that is no scalar's, an option's value that is not a
137
+ ; string, a number or a boolean.
138
+ ;
139
+ ; The tree is materialized whole before a line is written, since the
140
+ ; syntax comes first in the file and last in the tree, and a field's place
141
+ ; depends on the nested types after it. A pass over the events before
142
+ ; that gives every object's string \`name\` a member \`$name\` holding it as
143
+ ; a key, the one way alchemy compares two strings it did not write: a
144
+ ; field's type name is looked up in a nested type's \`$name\`. Nothing of
145
+ ; that member is written.
146
+
147
+ ; ---- the annotation
148
+
149
+ ; Each object's \`name\`, when it is a string, gets a member \`$name\` right
150
+ ; after it: an object whose one key is the name. The state is whether the
151
+ ; last event was the key \`name\`.
152
+ def proto-mark-step [s event]
153
+ match event
154
+ case (key name)
155
+ match name
156
+ case "name" (transition :name [event])
157
+ case _ (transition :other [event])
158
+ case (scalar value)
159
+ match s
160
+ case :name
161
+ match (kind value)
162
+ case :string (transition :other [event (key "$name") object-start (key value) (scalar true) object-end])
163
+ case _ (transition :other [event])
164
+ case _ (transition :other [event])
165
+ case _ (transition :other [event])
166
+
167
+ def proto-mark-finish [s] []
168
+
169
+ ; Whether the string s is the name of the object o.
170
+ def proto-named [s o]
171
+ match (kind (get "$name" o))
172
+ case :record
173
+ match (kind (get-path (as-path [s]) (get "$name" o)))
174
+ case :missing false
175
+ case _ true
176
+ case _ false
177
+
178
+ ; ---- members
179
+
180
+ def proto-has [k o]
181
+ match (kind (get k o))
182
+ case :missing false
183
+ case _ true
184
+
185
+ def proto-list [k o]
186
+ match (kind (get k o))
187
+ case :vector (as-vector (get k o))
188
+ case _ []
189
+
190
+ def proto-record [k o]
191
+ match (kind (get k o))
192
+ case :record (get k o)
193
+ case _ (record)
194
+
195
+ def proto-at [v i]
196
+ get-path (as-path [i]) v
197
+
198
+ ; A member the tree holds as a string, or a failure naming it.
199
+ def proto-text [what k o]
200
+ match (kind (get k o))
201
+ case :string (get k o)
202
+ case _ (fail :unrepresentable (string-join "" ["the tree is not a FileDescriptorProto: " what " has no string " k]))
203
+
204
+ def proto-name [what o]
205
+ proto-text what "name" o
206
+
207
+ def proto-nonempty [s]
208
+ match s
209
+ case "" false
210
+ case _ true
211
+
212
+ def proto-real-key [k]
213
+ match k
214
+ case "$name" false
215
+ case _ true
216
+
217
+ def proto-less [a b]
218
+ match (compare a b)
219
+ case :less true
220
+ case _ false
221
+
222
+ def proto-same-number [a b]
223
+ match (compare a b)
224
+ case :equal true
225
+ case _ false
226
+
227
+ def proto-member [i v]
228
+ match (count (filter (fn [x] (proto-same-number x i)) v))
229
+ case 0 false
230
+ case _ true
231
+
232
+ def proto-lines [items]
233
+ string-join "\\n" (filter proto-nonempty items)
234
+
235
+ def proto-deeper [indent]
236
+ string-join "" [indent " "]
237
+
238
+ ; A block: its head, then its body on the lines below it, or \`{}\` when the
239
+ ; body is empty.
240
+ def proto-block [indent head body]
241
+ match body
242
+ case "" (string-join "" [indent head " {}"])
243
+ case _ (string-join "" [indent head " {\\n" body "\\n" indent "}"])
244
+
245
+ ; ---- scalars
246
+
247
+ ; A number: its lexeme, or as the JSON renderer writes it; NaN, null in
248
+ ; JSON, as \`0_0\`, which the reader reads as NaN again.
249
+ def proto-num [n]
250
+ match (kind n)
251
+ case :number
252
+ match (number-class n)
253
+ case :finite (scalar-text csv-options n)
254
+ case :infinity "1e999"
255
+ case :negative-infinity "-1e999"
256
+ case :nan "0_0"
257
+ case :null "0_0"
258
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: a number is not one")
259
+
260
+ ; The characters a literal holds as they are between double quotes, and
261
+ ; between single quotes: none of the controls, and not that quote.
262
+ def proto-no-dq [[32 33] [35 1114111]]
263
+
264
+ def proto-no-sq [[32 38] [40 1114111]]
265
+
266
+ def proto-ident-chars [[48 57] [65 90] [95 95] [97 122]]
267
+
268
+ ; The characters of a bare constant: a number, a signed one, an identifier.
269
+ def proto-bare-chars [[43 43] [45 46] [48 57] [65 90] [95 95] [97 122]]
270
+
271
+ ; A string literal whose text between its quotes is s, as written.
272
+ def proto-literal [s]
273
+ match (chars-within proto-no-dq s)
274
+ case true (string-join "" ["\\"" s "\\""])
275
+ case false
276
+ match (chars-within proto-no-sq s)
277
+ case true (string-join "" ["'" s "'"])
278
+ case false (string-join "" ["\\"" s "\\""])
279
+
280
+ ; An option's string: a literal, or an aggregate where no literal holds it.
281
+ def proto-option-string [s]
282
+ match (chars-within proto-no-dq s)
283
+ case true (string-join "" ["\\"" s "\\""])
284
+ case false
285
+ match (chars-within proto-no-sq s)
286
+ case true (string-join "" ["'" s "'"])
287
+ case false (string-join "" ["{" s "}"])
288
+
289
+ ; The options descriptor.proto types as enums, whose values are identifiers.
290
+ def proto-enum-option [k]
291
+ match k
292
+ case "optimize_for" true
293
+ case "ctype" true
294
+ case "jstype" true
295
+ case "retention" true
296
+ case "targets" true
297
+ case "idempotency_level" true
298
+ case "features.field_presence" true
299
+ case "features.enum_type" true
300
+ case "features.repeated_field_encoding" true
301
+ case "features.utf8_validation" true
302
+ case "features.message_encoding" true
303
+ case "features.json_format" true
304
+ case "features.enforce_naming_style" true
305
+ case "features.default_symbol_visibility" true
306
+ case "features.(pb.cpp).string_type" true
307
+ case "features.(pb.java).utf8_validation" true
308
+ case "features.(pb.java).nest_in_file_class" true
309
+ case "features.(pb.go).api_level" true
310
+ case "features.(pb.go).strip_enum_prefix" true
311
+ case _ false
312
+
313
+ def proto-identifier [s]
314
+ match s
315
+ case "" false
316
+ case "true" false
317
+ case "false" false
318
+ case _ (chars-within proto-ident-chars s)
319
+
320
+ def proto-value [k v]
321
+ match (kind v)
322
+ case :boolean
323
+ match v
324
+ case true "true"
325
+ case false "false"
326
+ case :number (proto-num v)
327
+ case :null "1e999"
328
+ case :string
329
+ match (proto-enum-option k)
330
+ case true
331
+ match (proto-identifier v)
332
+ case true v
333
+ case false (proto-option-string v)
334
+ case false (proto-option-string v)
335
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: an option's value is not a string, a number or a boolean")
336
+
337
+ ; ---- options
338
+
339
+ def proto-option-lines [indent opts]
340
+ string-join "\\n"
341
+ map
342
+ fn [k]
343
+ string-join "" [indent "option " k " = " (proto-value k (get k opts)) ";"]
344
+ filter proto-real-key (keys opts)
345
+
346
+ def proto-option-items [opts]
347
+ string-join ", "
348
+ map
349
+ fn [k]
350
+ string-join "" [k " = " (proto-value k (get k opts))]
351
+ filter proto-real-key (keys opts)
352
+
353
+ def proto-brackets [items]
354
+ match items
355
+ case "" ""
356
+ case _ (string-join "" [" [" items "]"])
357
+
358
+ def proto-plain-options [o]
359
+ proto-brackets (proto-option-items (proto-record "options" o))
360
+
361
+ ; ---- fields
362
+
363
+ def proto-default [f]
364
+ let [v (proto-text "a field's default" "defaultValue" f)]
365
+ match (get "type" f)
366
+ case "TYPE_STRING" (proto-literal v)
367
+ case "TYPE_BYTES" (proto-literal v)
368
+ case _
369
+ match v
370
+ case "" (proto-literal v)
371
+ case _
372
+ match (chars-within proto-bare-chars v)
373
+ case true v
374
+ case false (proto-literal v)
375
+
376
+ def proto-field-options [f]
377
+ proto-brackets
378
+ string-join ", "
379
+ filter proto-nonempty
380
+ vector
381
+ match (proto-has "defaultValue" f)
382
+ case true (string-join "" ["default = " (proto-default f)])
383
+ case false ""
384
+ match (proto-has "jsonName" f)
385
+ case true (string-join "" ["json_name = " (proto-literal (proto-text "a field" "jsonName" f))])
386
+ case false ""
387
+ proto-option-items (proto-record "options" f)
388
+
389
+ def proto-scalar-type [t]
390
+ match t
391
+ case "TYPE_DOUBLE" "double"
392
+ case "TYPE_FLOAT" "float"
393
+ case "TYPE_INT64" "int64"
394
+ case "TYPE_UINT64" "uint64"
395
+ case "TYPE_INT32" "int32"
396
+ case "TYPE_FIXED64" "fixed64"
397
+ case "TYPE_FIXED32" "fixed32"
398
+ case "TYPE_BOOL" "bool"
399
+ case "TYPE_STRING" "string"
400
+ case "TYPE_BYTES" "bytes"
401
+ case "TYPE_UINT32" "uint32"
402
+ case "TYPE_SFIXED32" "sfixed32"
403
+ case "TYPE_SFIXED64" "sfixed64"
404
+ case "TYPE_SINT32" "sint32"
405
+ case "TYPE_SINT64" "sint64"
406
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: a field's type is no scalar a .proto file names")
407
+
408
+ def proto-type [f]
409
+ match (proto-has "type" f)
410
+ case true (proto-scalar-type (get "type" f))
411
+ case false (proto-text "a field" "typeName" f)
412
+
413
+ def proto-label [syntax f]
414
+ match (get "label" f)
415
+ case "LABEL_REQUIRED" "required "
416
+ case "LABEL_REPEATED" "repeated "
417
+ case _
418
+ match (get "proto3Optional" f)
419
+ case true "optional "
420
+ case _
421
+ match syntax
422
+ case "proto2" "optional "
423
+ case _ ""
424
+
425
+ def proto-field-line [syntax indent f labelled]
426
+ string-join ""
427
+ vector
428
+ indent
429
+ match labelled
430
+ case true (proto-label syntax f)
431
+ case false ""
432
+ proto-type f
433
+ " "
434
+ proto-name "a field" f
435
+ " = "
436
+ proto-num (get "number" f)
437
+ proto-field-options f
438
+ ";"
439
+
440
+ def proto-visibility [o]
441
+ match (get "visibility" o)
442
+ case "VISIBILITY_EXPORT" "export "
443
+ case "VISIBILITY_LOCAL" "local "
444
+ case _ ""
445
+
446
+ ; ---- ranges
447
+
448
+ ; The decimal digits, and the places of a number's text.
449
+ def proto-digits-down ["9" "8" "7" "6" "5" "4" "3" "2" "1"]
450
+
451
+ def proto-digits-up ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"]
452
+
453
+ def proto-places [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
454
+
455
+ def proto-places-of [text]
456
+ filter (fn [i] (proto-less i (length text))) proto-places
457
+
458
+ ; A digit after a prefix, a leading zero dropped.
459
+ def proto-append-digit [prefix d]
460
+ match [prefix d]
461
+ case ["" "0"] ""
462
+ case _ (string-join "" [prefix d])
463
+
464
+ ; The decimal text of n - 1 for an integer n of at least 1, a digit a
465
+ ; place from the left: at each place, the largest digit after which some
466
+ ; number is still below n, which is the one whose zeros after it are.
467
+ ; alchemy has no arithmetic, so the predecessor is found by comparison.
468
+ def proto-pred-pos [self places prefix n]
469
+ match (count places)
470
+ case 0
471
+ match prefix
472
+ case "" "0"
473
+ case _ prefix
474
+ case _
475
+ let [zeros (repeat (count (pop places)) "0")]
476
+ let [found (filter (fn [d] (proto-less (number (string-join "" [prefix d zeros])) n)) proto-digits-down)]
477
+ match (count found)
478
+ case 0 (self self (pop places) (proto-append-digit prefix "0") n)
479
+ case _ (self self (pop places) (proto-append-digit prefix (proto-at found 0)) n)
480
+
481
+ def proto-below-negated [prefix d nines n]
482
+ let [digits (string-join "" [(proto-append-digit prefix d) nines])]
483
+ match digits
484
+ case "" false
485
+ case _ (proto-less (number (string-join "" ["-" digits])) n)
486
+
487
+ ; The same for a negative n: the smallest m with -m below n, a digit a
488
+ ; place from the left, the smallest digit after which nines still are.
489
+ def proto-pred-neg [self places prefix n]
490
+ match (count places)
491
+ case 0 (string-join "" ["-" prefix])
492
+ case _
493
+ let [nines (repeat (count (pop places)) "9")]
494
+ let [found (filter (fn [d] (proto-below-negated prefix d nines n)) proto-digits-up)]
495
+ self self (pop places) (proto-append-digit prefix (proto-at found 0)) n
496
+
497
+ def proto-pred [n]
498
+ match (compare n 0)
499
+ case :greater (proto-pred-pos proto-pred-pos (proto-places-of (proto-num n)) "" n)
500
+ case :equal "-1"
501
+ case _ (proto-pred-neg proto-pred-neg (proto-places-of (proto-num n)) "" n)
502
+
503
+ ; A message's range, whose end is exclusive: \`start\`, \`start to last\` or
504
+ ; \`start to max\`.
505
+ def proto-range-ex [maxv r]
506
+ let [s (get "start" r)]
507
+ let [e (get "end" r)]
508
+ match (compare e maxv)
509
+ case :equal (string-join "" [(proto-num s) " to max"])
510
+ case _
511
+ let [p (proto-pred e)]
512
+ match (compare (number p) s)
513
+ case :equal (proto-num s)
514
+ case _ (string-join "" [(proto-num s) " to " p])
515
+
516
+ ; An enum's reserved range, whose end is inclusive.
517
+ def proto-range-in [r]
518
+ let [s (get "start" r)]
519
+ let [e (get "end" r)]
520
+ match (compare e s)
521
+ case :equal (proto-num s)
522
+ case _
523
+ match (compare e 2147483647)
524
+ case :equal (string-join "" [(proto-num s) " to max"])
525
+ case _ (string-join "" [(proto-num s) " to " (proto-num e)])
526
+
527
+ ; The end \`to max\` gives in a message.
528
+ def proto-max [m]
529
+ match (get "message_set_wire_format" (proto-record "options" m))
530
+ case true 2147483647
531
+ case _ 536870912
532
+
533
+ ; A reserved name: an identifier in an editions file, a literal before.
534
+ def proto-reserved-name [syntax n]
535
+ match syntax
536
+ case "editions"
537
+ match (proto-identifier n)
538
+ case true n
539
+ case false (proto-literal n)
540
+ case _ (proto-literal n)
541
+
542
+ def proto-reserved-names [syntax indent names]
543
+ match (count names)
544
+ case 0 ""
545
+ case _ (string-join "" [indent "reserved " (string-join ", " (map (fn [n] (proto-reserved-name syntax n)) names)) ";"])
546
+
547
+ def proto-reserved-ranges [indent text]
548
+ match text
549
+ case "" ""
550
+ case _ (string-join "" [indent "reserved " text ";"])
551
+
552
+ ; ---- messages
553
+
554
+ ; Whether two strings are the same. alchemy matches a string against a
555
+ ; literal and against no other string, but \`split\` cuts a string at every
556
+ ; occurrence of a separator that is not empty, so a string cut by itself,
557
+ ; and by nothing else, is two empty strings.
558
+ def proto-same-text [a b]
559
+ match a
560
+ case ""
561
+ match b
562
+ case "" true
563
+ case _ false
564
+ case _
565
+ match (split a b)
566
+ case ["" ""] true
567
+ case _ false
568
+
569
+ ; Whether v is a string and the string s.
570
+ def proto-is-text [s v]
571
+ match (kind v)
572
+ case :string (proto-same-text s v)
573
+ case _ false
574
+
575
+ ; Whether the string p begins with the string c, which is not empty, and p
576
+ ; without that first c.
577
+ def proto-begins [c p]
578
+ let [parts (split c p)]
579
+ match (proto-at parts 0)
580
+ case "" (proto-less 1 (count parts))
581
+ case _ false
582
+
583
+ def proto-after [c p]
584
+ let [parts (split c p)]
585
+ string-join c (map (fn [i] (proto-at parts i)) (filter (fn [i] (proto-less 0 i)) (indices parts)))
586
+
587
+ ; The letters protoc puts in upper case in an entry's name.
588
+ def proto-letters [["a" "A"] ["b" "B"] ["c" "C"] ["d" "D"] ["e" "E"] ["f" "F"] ["g" "G"] ["h" "H"] ["i" "I"] ["j" "J"] ["k" "K"] ["l" "L"] ["m" "M"] ["n" "N"] ["o" "O"] ["p" "P"] ["q" "Q"] ["r" "R"] ["s" "S"] ["t" "T"] ["u" "U"] ["v" "V"] ["w" "W"] ["x" "X"] ["y" "Y"] ["z" "Z"]]
589
+
590
+ ; A part of a name, its first letter in upper case where it is one from a
591
+ ; to z.
592
+ def proto-capital [p]
593
+ let [hits (filter (fn [l] (proto-begins (proto-at l 0) p)) proto-letters)]
594
+ match (count hits)
595
+ case 0 p
596
+ case _ (string-join "" [(proto-at (proto-at hits 0) 1) (proto-after (proto-at (proto-at hits 0) 0) p)])
597
+
598
+ ; The name protoc gives a map field's entry, which the reader gives the
599
+ ; entry it makes: the field's name with each \`_\` dropped and the letter
600
+ ; after it, and the first, in upper case, then \`Entry\` (\`map_field\` is
601
+ ; \`MapFieldEntry\`).
602
+ def proto-entry-name [name]
603
+ string-join "" [(string-join "" (map proto-capital (split "_" name))) "Entry"]
604
+
605
+ ; Whether every key of the object o is one of the keys allowed.
606
+ def proto-only [allowed o]
607
+ match (count (filter (fn [k] (proto-less (count (filter (fn [a] (proto-same-text a k)) allowed)) 1)) (keys o)))
608
+ case 0 true
609
+ case _ false
610
+
611
+ def proto-empty [k o]
612
+ match (kind (get k o))
613
+ case :vector
614
+ match (count (as-vector (get k o)))
615
+ case 0 true
616
+ case _ false
617
+ case _ false
618
+
619
+ ; The options the reader copies from a map field to its entry's key and
620
+ ; value: \`features\`, and those whose name begins \`features.\`.
621
+ def proto-feature-key [k]
622
+ match k
623
+ case "features" true
624
+ case _ (proto-begins "features." k)
625
+
626
+ ; Whether two options' values are the same: of one kind, and alike.
627
+ def proto-same-value [a b]
628
+ match [(kind a) (kind b)]
629
+ case [:string :string] (proto-same-text a b)
630
+ case [:number :number] (proto-same-number a b)
631
+ case [:boolean :boolean]
632
+ match [a b]
633
+ case [true true] true
634
+ case [false false] true
635
+ case _ false
636
+ case [:record :record] (proto-same-text (scalar-text csv-options a) (scalar-text csv-options b))
637
+ case _ false
638
+
639
+ ; Whether a field of an entry has the options the reader gives it: the
640
+ ; features of the map field fo, by name, \`feats\`, and none where it has
641
+ ; none.
642
+ def proto-entry-options [fo feats g]
643
+ match [(count feats) (proto-has "options" g)]
644
+ case [0 false] true
645
+ case [0 true] false
646
+ case [_ false] false
647
+ case _
648
+ let [o (get "options" g)]
649
+ match (kind o)
650
+ case :record
651
+ match (proto-same-number (count (keys o)) (count feats))
652
+ case true (proto-same-number (count (filter (fn [k] (proto-same-value (get k o) (get k fo))) feats)) (count feats))
653
+ case false false
654
+ case _ false
655
+
656
+ def proto-entry-field-keys ["name" "$name" "number" "label" "type" "typeName" "options"]
657
+
658
+ ; Whether g is a field of an entry as the reader makes it, \`key\` 1 or
659
+ ; \`value\` 2: optional, of a type or a type name, and with no option but
660
+ ; the features the map field passes down.
661
+ def proto-entry-field [name number fo feats g]
662
+ match (kind g)
663
+ case :record
664
+ match [(proto-is-text name (get "name" g)) (get "label" g) (kind (get "number" g)) (proto-has "type" g) (proto-has "typeName" g) (proto-only proto-entry-field-keys g)]
665
+ case [true "LABEL_OPTIONAL" :number true false true]
666
+ match (proto-same-number (get "number" g) number)
667
+ case true (proto-entry-options fo feats g)
668
+ case false false
669
+ case [true "LABEL_OPTIONAL" :number false true true]
670
+ match (proto-same-number (get "number" g) number)
671
+ case true (proto-entry-options fo feats g)
672
+ case false false
673
+ case _ false
674
+ case _ false
675
+
676
+ def proto-entry-keys ["name" "$name" "field" "nestedType" "enumType" "oneofDecl" "extension" "options"]
677
+
678
+ ; Whether the nested type n is the entry the reader makes for the map
679
+ ; field f, so that f written as a map reads back with n as it is: n named
680
+ ; for f by protoc's rule, with no member but its fields, its empty lists
681
+ ; and the option \`mapEntry\` alone, and its fields \`key\` and \`value\` as the
682
+ ; reader makes them. A message the file declares, the option \`mapEntry\`
683
+ ; in it, is any other one, and is written as a message.
684
+ def proto-map-entry [f n]
685
+ match [(kind (get "name" f)) (kind (get "name" n))]
686
+ case [:string :string]
687
+ match [(proto-same-text (proto-entry-name (get "name" f)) (get "name" n)) (proto-only proto-entry-keys n)]
688
+ case [true true]
689
+ let [o (proto-record "options" n)]
690
+ match [(proto-empty "nestedType" n) (proto-empty "enumType" n) (proto-empty "oneofDecl" n) (proto-empty "extension" n) (count (keys o)) (get "mapEntry" o)]
691
+ case [true true true true 1 true]
692
+ let [kv (proto-list "field" n)]
693
+ match (count kv)
694
+ case 2
695
+ let [fo (proto-record "options" f)]
696
+ let [feats (filter proto-feature-key (keys fo))]
697
+ match [(proto-entry-field "key" 1 fo feats (proto-at kv 0)) (proto-entry-field "value" 2 fo feats (proto-at kv 1))]
698
+ case [true true] true
699
+ case _ false
700
+ case _ false
701
+ case _ false
702
+ case _ false
703
+ case _ false
704
+
705
+ def proto-first [v]
706
+ match (count v)
707
+ case 0 -1
708
+ case _ (proto-at v 0)
709
+
710
+ def proto-last [v]
711
+ match (count v)
712
+ case 0 -1
713
+ case _ (top v)
714
+
715
+ def proto-entry-named [f n]
716
+ match (proto-named (get "typeName" f) n)
717
+ case true (proto-map-entry f n)
718
+ case false false
719
+
720
+ ; The nested type a field is written with, its index, or -1: a group's
721
+ ; message, and a map field's entry. A map field is a repeated field whose
722
+ ; type is named for a nested type that is the entry the reader would make
723
+ ; for it (\`proto-map-entry\`).
724
+ def proto-attached [ns f]
725
+ match (get "type" f)
726
+ case "TYPE_GROUP" (proto-first (filter (fn [t] (proto-named (get "typeName" f) (proto-at ns t))) (indices ns)))
727
+ case _
728
+ match (get "label" f)
729
+ case "LABEL_REPEATED"
730
+ match [(proto-has "type" f) (proto-has "typeName" f) (proto-has "oneofIndex" f) (proto-has "extendee" f)]
731
+ case [false true false false] (proto-first (filter (fn [t] (proto-entry-named f (proto-at ns t))) (indices ns)))
732
+ case _ -1
733
+ case _ -1
734
+
735
+ ; Whether a field is in a declared oneof: a proto3 \`optional\` field's
736
+ ; oneof is synthetic, and the field is written \`optional\`.
737
+ def proto-in-oneof [f]
738
+ match (proto-has "oneofIndex" f)
739
+ case true
740
+ match (get "proto3Optional" f)
741
+ case true false
742
+ case _ true
743
+ case false false
744
+
745
+ def proto-same-oneof [fs j k]
746
+ let [a (proto-at fs j)]
747
+ let [b (proto-at fs k)]
748
+ match (proto-in-oneof b)
749
+ case true (proto-same-number (get "oneofIndex" a) (get "oneofIndex" b))
750
+ case false false
751
+
752
+ ; A unit is a field, or a declared oneof's fields, which are adjacent in
753
+ ; the tree; it is written at its first field.
754
+ def proto-unit-start [fs j]
755
+ match (proto-in-oneof (proto-at fs j))
756
+ case false true
757
+ case true
758
+ match (count (filter (fn [k] (proto-less k j)) (filter (fn [k] (proto-same-oneof fs j k)) (indices fs))))
759
+ case 0 true
760
+ case _ false
761
+
762
+ def proto-unit-fields [fs j]
763
+ match (proto-in-oneof (proto-at fs j))
764
+ case false [j]
765
+ case true (filter (fn [k] (proto-same-oneof fs j k)) (indices fs))
766
+
767
+ def proto-attached-of [att ks]
768
+ filter (fn [t] (proto-less -1 t)) (map (fn [k] (proto-at att k)) ks)
769
+
770
+ ; The nested types written as messages, between two attached ones.
771
+ def proto-plain-nested [ns att lo hi]
772
+ filter
773
+ fn [t]
774
+ match (proto-member t att)
775
+ case true false
776
+ case false
777
+ match [(proto-less lo t) (proto-less t hi)]
778
+ case [true true] true
779
+ case _ false
780
+ indices ns
781
+
782
+ ; The messages due before a unit: those after the nested type of the last
783
+ ; attached field before it and before the unit's own first.
784
+ def proto-nested-before [indent ns bodies fs att j]
785
+ let [mine (proto-attached-of att (proto-unit-fields fs j))]
786
+ match (count mine)
787
+ case 0 ""
788
+ case _
789
+ let [lo (proto-last (proto-attached-of att (filter (fn [k] (proto-less k j)) (indices fs))))]
790
+ string-join "\\n"
791
+ map (fn [t] (proto-message indent (proto-at ns t) (proto-at bodies t))) (proto-plain-nested ns att lo (proto-at mine 0))
792
+
793
+ def proto-nested-after [indent ns bodies att]
794
+ let [lo (proto-last (filter (fn [t] (proto-less -1 t)) att))]
795
+ string-join "\\n"
796
+ map (fn [t] (proto-message indent (proto-at ns t) (proto-at bodies t))) (proto-plain-nested ns att lo 4294967296)
797
+
798
+ ; The declared oneofs: all but the synthetic ones of proto3 \`optional\`.
799
+ def proto-declared-oneofs [m]
800
+ let [synthetic (map (fn [f] (get "oneofIndex" f)) (filter (fn [f] (match (get "proto3Optional" f) (case true (proto-has "oneofIndex" f)) (case _ false))) (proto-list "field" m)))]
801
+ filter (fn [i] (match (proto-member i synthetic) (case true false) (case false true))) (indices (proto-list "oneofDecl" m))
802
+
803
+ ; The declared oneofs no field is in.
804
+ def proto-empty-oneofs [m]
805
+ let [used (map (fn [f] (get "oneofIndex" f)) (filter proto-in-oneof (proto-list "field" m)))]
806
+ filter (fn [i] (match (proto-member i used) (case true false) (case false true))) (proto-declared-oneofs m)
807
+
808
+ def proto-oneof-block [indent m i members]
809
+ let [o (proto-at (proto-list "oneofDecl" m) i)]
810
+ proto-block indent (string-join "" ["oneof " (proto-name "a oneof" o)])
811
+ proto-lines
812
+ vector
813
+ proto-option-lines (proto-deeper indent) (proto-record "options" o)
814
+ members
815
+
816
+ ; The empty oneofs due before a unit's oneof: those after the last oneof
817
+ ; written before it.
818
+ def proto-oneofs-before [indent m fs j]
819
+ let [f (proto-at fs j)]
820
+ match (proto-in-oneof f)
821
+ case false ""
822
+ case true
823
+ let [lo (proto-last (map (fn [k] (get "oneofIndex" (proto-at fs k))) (filter (fn [k] (match (proto-less k j) (case true (proto-in-oneof (proto-at fs k))) (case false false))) (indices fs))))]
824
+ string-join "\\n"
825
+ map
826
+ fn [i]
827
+ proto-oneof-block indent m i ""
828
+ filter (fn [i] (match [(proto-less lo i) (proto-less i (get "oneofIndex" f))] (case [true true] true) (case _ false))) (proto-empty-oneofs m)
829
+
830
+ def proto-oneofs-after [indent m]
831
+ let [lo (proto-last (map (fn [f] (get "oneofIndex" f)) (filter proto-in-oneof (proto-list "field" m))))]
832
+ string-join "\\n"
833
+ map
834
+ fn [i]
835
+ proto-oneof-block indent m i ""
836
+ filter (fn [i] (proto-less lo i)) (proto-empty-oneofs m)
837
+
838
+ def proto-group-block [syntax indent f body labelled]
839
+ proto-block indent
840
+ string-join ""
841
+ vector
842
+ match labelled
843
+ case true (proto-label syntax f)
844
+ case false ""
845
+ "group "
846
+ proto-text "a group" "typeName" f
847
+ " = "
848
+ proto-num (get "number" f)
849
+ body
850
+
851
+ def proto-map-line [indent f n]
852
+ let [kv (proto-list "field" n)]
853
+ string-join ""
854
+ vector
855
+ indent
856
+ "map<"
857
+ proto-type (proto-at kv 0)
858
+ ", "
859
+ proto-type (proto-at kv 1)
860
+ "> "
861
+ proto-name "a map field" f
862
+ " = "
863
+ proto-num (get "number" f)
864
+ proto-field-options f
865
+ ";"
866
+
867
+ ; A field's statement: a field, a map field, or a group with its body. A
868
+ ; group outside a oneof is written with the body the message's own were
869
+ ; written with; one in a oneof, a level deeper, has its body written there.
870
+ def proto-field-statement [self syntax indent ns bodies att fs k labelled]
871
+ let [f (proto-at fs k)]
872
+ let [t (proto-at att k)]
873
+ match (proto-less t 0)
874
+ case true (proto-field-line syntax indent f labelled)
875
+ case false
876
+ match (get "type" f)
877
+ case "TYPE_GROUP"
878
+ match labelled
879
+ case true (proto-group-block syntax indent f (proto-at bodies t) labelled)
880
+ case false (proto-group-block syntax indent f (self self syntax (proto-deeper indent) (proto-at ns t)) labelled)
881
+ case _ (proto-map-line indent f (proto-at ns t))
882
+
883
+ def proto-unit-text [self syntax indent m ns bodies fs att j]
884
+ let [f (proto-at fs j)]
885
+ match (proto-in-oneof f)
886
+ case false (proto-field-statement self syntax indent ns bodies att fs j true)
887
+ case true
888
+ proto-oneof-block indent m (get "oneofIndex" f)
889
+ string-join "\\n"
890
+ map (fn [k] (proto-field-statement self syntax (proto-deeper indent) ns bodies att fs k false)) (proto-unit-fields fs j)
891
+
892
+ def proto-unit [self syntax indent m ns bodies fs att j]
893
+ match (proto-unit-start fs j)
894
+ case false ""
895
+ case true
896
+ proto-lines
897
+ vector
898
+ proto-nested-before indent ns bodies fs att j
899
+ proto-oneofs-before indent m fs j
900
+ proto-unit-text self syntax indent m ns bodies fs att j
901
+
902
+ ; A message's extension ranges, reserved ranges and reserved names, in the
903
+ ; order the tree holds them.
904
+ def proto-range-key [syntax indent maxv m k]
905
+ match k
906
+ case "extensionRange"
907
+ string-join "\\n"
908
+ map
909
+ fn [r]
910
+ string-join "" [indent "extensions " (proto-range-ex maxv r) (proto-plain-options r) ";"]
911
+ proto-list "extensionRange" m
912
+ case "reservedRange" (proto-reserved-ranges indent (string-join ", " (map (fn [r] (proto-range-ex maxv r)) (proto-list "reservedRange" m))))
913
+ case "reservedName" (proto-reserved-names syntax indent (proto-list "reservedName" m))
914
+ case _ ""
915
+
916
+ ; A message's body, the one definition the render recurses through, as
917
+ ; \`self\`. Every nested type's body is written first, a level deeper, and
918
+ ; the rest of the body places them: the recursion then nests a few
919
+ ; evaluation levels a message level, well within alchemy's bound for the
920
+ ; 100 levels the reader accepts, where placing a message among the fields
921
+ ; first would nest some twenty.
922
+ def proto-message-body [self syntax indent m]
923
+ let [ns (proto-list "nestedType" m)]
924
+ let [bodies (map (fn [n] (self self syntax (proto-deeper indent) n)) ns)]
925
+ proto-message-parts self syntax indent m ns bodies
926
+
927
+ def proto-message-parts [self syntax indent m ns bodies]
928
+ let [fs (proto-list "field" m)]
929
+ let [att (map (fn [f] (proto-attached ns f)) fs)]
930
+ proto-lines
931
+ vector
932
+ proto-option-lines indent (proto-record "options" m)
933
+ proto-lines (map (fn [j] (proto-unit self syntax indent m ns bodies fs att j)) (indices fs))
934
+ proto-nested-after indent ns bodies att
935
+ proto-oneofs-after indent m
936
+ string-join "\\n" (map (fn [e] (proto-enum syntax indent e)) (proto-list "enumType" m))
937
+ string-join "\\n" (map (fn [f] (proto-extend syntax indent f)) (proto-list "extension" m))
938
+ proto-lines (map (fn [k] (proto-range-key syntax indent (proto-max m) m k)) (keys m))
939
+
940
+ ; A message's block around its body.
941
+ def proto-message [indent m body]
942
+ proto-block indent (string-join "" [(proto-visibility m) "message " (proto-name "a message" m)]) body
943
+
944
+ ; ---- enums, services, extensions
945
+
946
+ def proto-enum-key [syntax indent e k]
947
+ match k
948
+ case "value"
949
+ string-join "\\n"
950
+ map
951
+ fn [v]
952
+ string-join "" [indent (proto-name "an enum value" v) " = " (proto-num (get "number" v)) (proto-plain-options v) ";"]
953
+ proto-list "value" e
954
+ case "options" (proto-option-lines indent (proto-record "options" e))
955
+ case "reservedRange" (proto-reserved-ranges indent (string-join ", " (map proto-range-in (proto-list "reservedRange" e))))
956
+ case "reservedName" (proto-reserved-names syntax indent (proto-list "reservedName" e))
957
+ case _ ""
958
+
959
+ def proto-enum [syntax indent e]
960
+ proto-block indent (string-join "" [(proto-visibility e) "enum " (proto-name "an enum" e)])
961
+ proto-lines (map (fn [k] (proto-enum-key syntax (proto-deeper indent) e k)) (keys e))
962
+
963
+ def proto-stream [flag]
964
+ match flag
965
+ case true "stream "
966
+ case _ ""
967
+
968
+ def proto-method [indent r]
969
+ let [head (string-join "" ["rpc " (proto-name "a method" r) " (" (proto-stream (get "clientStreaming" r)) (proto-text "a method" "inputType" r) ") returns (" (proto-stream (get "serverStreaming" r)) (proto-text "a method" "outputType" r) ")"])]
970
+ match (proto-has "options" r)
971
+ case false (string-join "" [indent head ";"])
972
+ case true (proto-block indent head (proto-option-lines (proto-deeper indent) (proto-record "options" r)))
973
+
974
+ def proto-service [indent s]
975
+ proto-block indent (string-join "" ["service " (proto-name "a service" s)])
976
+ proto-lines
977
+ vector
978
+ proto-option-lines (proto-deeper indent) (proto-record "options" s)
979
+ string-join "\\n" (map (fn [r] (proto-method (proto-deeper indent) r)) (proto-list "method" s))
980
+
981
+ ; An extension, in an \`extend\` block of its own.
982
+ def proto-extend [syntax indent f]
983
+ proto-block indent (string-join "" ["extend " (proto-text "an extension" "extendee" f)])
984
+ proto-field-line syntax (proto-deeper indent) f true
985
+
986
+ ; ---- the file
987
+
988
+ def proto-syntax-line [file]
989
+ match (get "syntax" file)
990
+ case "proto2" "syntax = \\"proto2\\";"
991
+ case "proto3" "syntax = \\"proto3\\";"
992
+ case "editions"
993
+ match (get "edition" file)
994
+ case "EDITION_2023" "edition = \\"2023\\";"
995
+ case "EDITION_2024" "edition = \\"2024\\";"
996
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its edition is not one a .proto file declares")
997
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its syntax is not proto2, proto3 or editions")
998
+
999
+ def proto-import-kind [file i]
1000
+ match (proto-member i (proto-list "publicDependency" file))
1001
+ case true "public "
1002
+ case false
1003
+ match (proto-member i (proto-list "weakDependency" file))
1004
+ case true "weak "
1005
+ case false ""
1006
+
1007
+ def proto-dependency [file i d]
1008
+ match (kind d)
1009
+ case :string (string-join "" ["import " (proto-import-kind file i) (proto-literal d) ";"])
1010
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: an import is not a string")
1011
+
1012
+ ; The package, the imports and the file's options, in the order the tree
1013
+ ; holds them.
1014
+ def proto-head-key [file k]
1015
+ match k
1016
+ case "package" (string-join "" ["package " (proto-text "the file" "package" file) ";"])
1017
+ case "dependency"
1018
+ let [ds (proto-list "dependency" file)]
1019
+ string-join "\\n" (map (fn [i] (proto-dependency file i (proto-at ds i))) (indices ds))
1020
+ case "optionDependency"
1021
+ string-join "\\n"
1022
+ map
1023
+ fn [d]
1024
+ string-join "" ["import option " (proto-literal d) ";"]
1025
+ proto-list "optionDependency" file
1026
+ case "options" (proto-option-lines "" (proto-record "options" file))
1027
+ case _ ""
1028
+
1029
+ def proto-file [file]
1030
+ match (kind file)
1031
+ case :record
1032
+ let [syntax (get "syntax" file)]
1033
+ string-join ""
1034
+ vector
1035
+ proto-lines
1036
+ vector
1037
+ proto-syntax-line file
1038
+ proto-lines (map (fn [k] (proto-head-key file k)) (keys file))
1039
+ string-join "\\n" (map (fn [m] (proto-message "" m (proto-message-body proto-message-body syntax " " m))) (proto-list "messageType" file))
1040
+ string-join "\\n" (map (fn [e] (proto-enum syntax "" e)) (proto-list "enumType" file))
1041
+ string-join "\\n" (map (fn [s] (proto-service "" s)) (proto-list "service" file))
1042
+ string-join "\\n" (map (fn [f] (proto-extend syntax "" f)) (proto-list "extension" file))
1043
+ "\\n"
1044
+ case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its root is not an object")
1045
+
1046
+ ; The render: the tree's events in, the file's text out.
1047
+ def proto-render [input]
1048
+ concat-map proto-file
1049
+ select root
1050
+ as-events (scan-emit :other proto-mark-step proto-mark-finish (events input))
1051
+ ` })
1052
+ })
1053
+
1054
+ function translate (): TranslationParts | undefined {
1055
+ return TRANSLATION
1056
+ }
1057
+
1058
+ export {
1059
+ translate,
1060
+ }
1061
+
1062
+ export type {
1063
+ TranslationPart,
1064
+ TranslationParts,
1065
+ }