@tabnas/proto 0.6.5 → 0.6.7

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