@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.
- package/dist/proto.d.ts +3 -1
- package/dist/proto.js +4 -2
- package/dist/proto.js.map +1 -1
- package/dist/translate.d.ts +13 -0
- package/dist/translate.js +1043 -0
- package/dist/translate.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/proto.ts +3 -1
- package/src/translate.ts +1065 -0
package/src/translate.ts
ADDED
|
@@ -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
|
+
}
|