@tabnas/proto 0.6.11 → 0.6.12

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/src/translate.ts CHANGED
@@ -13,6 +13,7 @@ type TranslationParts = Readonly<{
13
13
  lift?: TranslationPart
14
14
  embed?: TranslationPart
15
15
  render?: TranslationPart
16
+ transduce?: TranslationPart
16
17
  }>
17
18
 
18
19
  const TRANSLATION: TranslationParts = Object.freeze({
@@ -47,1129 +48,988 @@ const TRANSLATION: TranslationParts = Object.freeze({
47
48
  "reads": "tree",
48
49
  "writes": "tree",
49
50
  "root": "object",
50
- "schema": "proto-descriptor",
51
+ "schema": "proto-ast",
51
52
  "render": "alchemy/render.alc",
53
+ "transduce": "alchemy/transduce.alc",
52
54
  "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 the quotes whose every occurrence in it is escaped; a backtick string holding a line break, which the descriptor keeps with its backticks and no quotes hold, is written as itself, but for a reserved name, which the reader takes only from a quoted literal or an identifier; and a text that nothing holds where it goes is refused: a control character that no literal or backtick string holds (a tab, a line break outside a backtick string, the other C0 controls), both quotes unescaped, or a backslash at its end that would escape the closing quote.",
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 the backtick string it is where it is one, as an aggregate only where neither can hold its text (a line break, or both quotes unescaped), 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; a value no literal or backtick string holds that is no aggregate's body, which no file's descriptor holds, is written in braces too and does not read back; and a value holding a control character that no literal, backtick string or aggregate holds is refused.",
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
- "whole": "A file's descriptor gathers its imports, messages, enums, services and extensions into a list of each kind, which a declaration anywhere in the file adds to, so a document's tree is complete only at its end: it is read whole before anything is written."
55
+ "The file is written anew from its tree: comments, blank lines and white space are not kept, and each declaration is written on a line of its own, a block's declarations indented two spaces deeper than the line that opens it.",
56
+ "An empty statement, a semicolon on its own, declares nothing and is not kept.",
57
+ "A scalar is kept as the text its tokens spell, run together, so white space between the tokens of one is not: a sign is written against its number, a qualified name with no space around its dots, and adjacent string literals with nothing between them.",
58
+ "An option's aggregate value in braces is kept as its fields, so its layout, its comments and the separators and colons between its fields are not: it is written on one line, a field holding an aggregate as name { ... } and any other as name: value."
59
+ ]
65
60
  }
66
61
  }
67
62
  `,
68
- render: Object.freeze({ entry: "proto-render", source: `; Protocol Buffers' render: the reader's tree, a FileDescriptorProto, written
69
- ; back as one \`.proto\` file. This is a library of definitions, each named
70
- ; \`proto-...\`, which a host links with its own program (alchemy's
71
- ; \`compile_sources\`); its entry point is \`proto-render\`, and nothing else
63
+ render: Object.freeze({ entry: "proto-render", source: `; Protocol Buffers' render: the source-ordered tree, the schema
64
+ ; \`proto-ast\`, written back as one \`.proto\` file, a declaration at a time
65
+ ; as its events arrive. This is a library of definitions, each named
66
+ ; \`proto-render-...\` but its entry point, \`proto-render\`, which a host
67
+ ; links with its own program (alchemy's \`compile_sources\`); nothing else
72
68
  ; here is the host's to call.
73
69
  ;
74
- ; The render writes the reader's own tree and no other, as the
75
- ; translation design says a render of a format whose value has a shape of
76
- ; its own does: the manifest names that shape as the schema
77
- ; \`proto-descriptor\`, and a host composes into this render only from a
78
- ; source of the same schema or from a program that builds the tree. The
79
- ; contract is the round trip: the text it writes reads back, through this
80
- ; repository's reader, as the tree it was given. The tree keeps what a
81
- ; descriptor holds and not how the file spelled it, so the render chooses
82
- ; a spelling for each part, the one that reads back the same, as the
83
- ; manifest's loss list says:
70
+ ; The tree is the file's declarations in source order (alchemy's
71
+ ; docs/streaming-plan.md, section 13.1): \`{"type": "file", "body": [...]}\`,
72
+ ; each declaration an object whose \`type\` comes first and whose other
73
+ ; members follow in the order the statement spells them:
84
74
  ;
85
- ; - The file is the \`syntax\` or \`edition\` declaration, then the package,
86
- ; the imports (\`public\`, \`weak\`, and edition 2024's \`import option\`) and
87
- ; the file's options, in the order the tree holds them, then the
88
- ; messages, the enums, the services and the extensions, each a statement
89
- ; or a block on lines of its own, indented two spaces a level.
90
- ; - A message's body is its options, then its fields in order, each nested
91
- ; message written where it keeps its place among the fields' own (a map
92
- ; field's entry and a group's message are nested types too, written as
93
- ; the map field and the group), the declared oneofs as blocks around
94
- ; their fields (a proto3 \`optional\` field's synthetic oneof is not one:
95
- ; the field is written \`optional\`), then its enums, its extensions (an
96
- ; \`extend\` block each), its extension ranges and its reserved ranges and
97
- ; names, in the order the tree holds them. A map field's entry is a
98
- ; nested type the reader would make for the field again: named for it
99
- ; by protoc's rule and like the reader's entry in every member. Any
100
- ; other, a message the file declared with the option \`mapEntry\` among
101
- ; them, is written as a message, and the field as a field of its type.
102
- ; - A field is its label (none in a oneof; \`optional\` for a proto2 field
103
- ; the tree marks optional, and for a proto3 field the tree marks
104
- ; \`proto3Optional\`; nothing for the other optional fields), its type (a
105
- ; scalar's keyword, or the type name as written), its name, its number
106
- ; and its options in brackets: \`default\`, \`json_name\` and the rest.
107
- ; - An exclusive range end (a message's reserved and extension ranges) is
108
- ; written one below the tree's, or \`max\` where it is the bound \`to max\`
109
- ; gives (536870912, or 2147483647 under \`message_set_wire_format\`); an
110
- ; enum's inclusive end as it is, \`max\` for 2147483647.
111
- ; - A string from a string literal is kept as its text between the quotes,
112
- ; escapes as written, so it is written between double quotes, or single
113
- ; quotes where it holds a double quote and no single quote, and where it
114
- ; holds both, between the quotes it escapes (an escaped backslash, \`\\\\\`,
115
- ; escapes no quote after it). A backtick string, which the lexer reads
116
- ; across lines too, is kept as written, backticks and all, so one
117
- ; holding a line break, which no quotes hold, is written as itself. A
118
- ; reserved name is not: the reader takes one from a quoted literal or an
119
- ; identifier, and the words of a backtick string as names of their own.
120
- ; An option's value the reader read as an identifier, a quoted literal,
121
- ; a backtick string or an aggregate in braces is one string to the tree:
122
- ; it is written quoted, as the backtick string it is, as an aggregate
123
- ; (\`{...}\`) where neither can hold it (a line break, or both quotes
124
- ; unescaped), and as an identifier for the options descriptor.proto
125
- ; types as enums (\`optimize_for\`, \`ctype\`, \`jstype\`, \`retention\`,
126
- ; \`targets\`, \`idempotency_level\` and the features) where it is made of
127
- ; identifier characters; a value a program made that no literal or
128
- ; backtick string holds and that is no aggregate's body is written in
129
- ; braces too, and the reader refuses it, since alchemy cannot parse an
130
- ; aggregate. A default is written bare for a field that is not a string
131
- ; or bytes, where it is made of the characters a constant is, and as a
132
- ; literal otherwise. alchemy cannot read a string a character at a time,
133
- ; so two texts a file can hold, each refused by protoc, do not read back:
134
- ; a quoted value of an enum-typed option that begins with a digit, and a
135
- ; quoted default of a field that is not a string whose text is a
136
- ; constant's characters spelling no one constant.
137
- ; - A field or enum number the reader could not read (JavaScript's Number
138
- ; of \`1_0\`, the lexer's digit separator) is NaN, null in JSON, and is
139
- ; written \`0_0\`, which reads back the same; an option's number that is
140
- ; null in JSON is infinite, and is written \`1e999\`.
75
+ ; syntax, edition value
76
+ ; import modifier (where written), path
77
+ ; package name
78
+ ; option name, value
79
+ ; message, enum visibility (where written), name, body
80
+ ; service, oneof name, body
81
+ ; extend extendee, body
82
+ ; field label (where written), fieldType, name, number,
83
+ ; options (where written)
84
+ ; map keyType, valueType, name, number, options
85
+ ; group label, name, number, body
86
+ ; extensions ranges, options
87
+ ; reserved ranges or names
88
+ ; enumValue name, number, options
89
+ ; rpc name, inputStream (true, where written), inputType,
90
+ ; outputStream, outputType, body (where written)
141
91
  ;
142
- ; Anything else fails with TARGET_VALUE_UNREPRESENTABLE, naming what was
143
- ; met: a root that is not an object, a syntax or an edition a \`.proto\`
144
- ; file cannot declare, a member a descriptor holds as a string that is not
145
- ; one, a field type that is no scalar's, an option's value that is not a
146
- ; string, a number or a boolean, and a string that nothing a \`.proto\` file
147
- ; writes holds where the string goes: a control character (no literal
148
- ; holds one, a backtick string only a line break, and an option's
149
- ; aggregate only the tab and line breaks between its tokens), and outside
150
- ; an option's value, both quotes unescaped or a backslash at the end,
151
- ; which would escape the closing quote.
92
+ ; A scalar is the text the source spells, a string literal with its
93
+ ; quotes, so each is written as it is. A range is \`from\` and, where it has
94
+ ; one, \`to\`; an option in brackets is \`name\` and \`value\`; an option's value
95
+ ; is its text or an aggregate's fields, each \`name\` and \`value\`, written in
96
+ ; braces on one line, a field holding an aggregate as \`name { ... }\` and
97
+ ; any other as \`name: value\`.
152
98
  ;
153
- ; The tree is materialized whole before a line is written, since the
154
- ; syntax comes first in the file and last in the tree, and a field's place
155
- ; depends on the nested types after it. A pass over the events before
156
- ; that gives every object's string \`name\` a member \`$name\` holding it as
157
- ; a key, the one way alchemy compares two strings it did not write: a
158
- ; field's type name is looked up in a nested type's \`$name\`. Nothing of
159
- ; that member is written.
160
-
161
- ; ---- the annotation
162
-
163
- ; Each object's \`name\`, when it is a string, gets a member \`$name\` right
164
- ; after it: an object whose one key is the name. The state is whether the
165
- ; last event was the key \`name\`.
166
- def proto-mark-step [s event]
167
- match event
168
- case (key name)
169
- match name
170
- case "name" (transition :name [event])
171
- case _ (transition :other [event])
172
- case (scalar value)
173
- match s
174
- case :name
175
- match (kind value)
176
- case :string (transition :other [event (key "$name") object-start (key value) (scalar true) object-end])
177
- case _ (transition :other [event])
178
- case _ (transition :other [event])
179
- case _ (transition :other [event])
180
-
181
- def proto-mark-finish [s] []
182
-
183
- ; Whether the string s is the name of the object o.
184
- def proto-named [s o]
185
- match (kind (get "$name" o))
186
- case :record
187
- match (kind (get-path (as-path [s]) (get "$name" o)))
188
- case :missing false
189
- case _ true
190
- case _ false
191
-
192
- ; ---- members
193
-
194
- def proto-has [k o]
195
- match (kind (get k o))
196
- case :missing false
197
- case _ true
198
-
199
- def proto-list [k o]
200
- match (kind (get k o))
201
- case :vector (as-vector (get k o))
202
- case _ []
203
-
204
- def proto-record [k o]
205
- match (kind (get k o))
206
- case :record (get k o)
207
- case _ (record)
208
-
209
- def proto-at [v i]
210
- get-path (as-path [i]) v
211
-
212
- ; A member the tree holds as a string, or a failure naming it.
213
- def proto-text [what k o]
214
- match (kind (get k o))
215
- case :string (get k o)
216
- case _ (fail :unrepresentable (string-join "" ["the tree is not a FileDescriptorProto: " what " has no string " k]))
217
-
218
- def proto-name [what o]
219
- proto-text what "name" o
220
-
221
- def proto-nonempty [s]
222
- match s
223
- case "" false
224
- case _ true
225
-
226
- def proto-real-key [k]
227
- match k
228
- case "$name" false
229
- case _ true
230
-
231
- def proto-less [a b]
232
- match (compare a b)
233
- case :less true
234
- case _ false
235
-
236
- def proto-same-number [a b]
237
- match (compare a b)
238
- case :equal true
99
+ ; The text: each declaration on a line of its own, a block's declarations
100
+ ; two spaces deeper than the line that opens it, and the block's closing
101
+ ; brace on a line of its own; an empty file is no text at all. The
102
+ ; contract is the round trip: the text reads back, through this
103
+ ; repository's reader, as the tree it was written from. The render checks
104
+ ; the tree's shape (each node's type, its members and their order, a body
105
+ ; holding only what its parent's block holds) and writes each scalar as it
106
+ ; is, so a tree the reader could not have built (a name holding a space,
107
+ ; a type that is a keyword where it stands) is written as its members say
108
+ ; and does not read back as itself. A root that is no file, a type, a
109
+ ; member or a value no node of the schema has, a member out of its place
110
+ ; or repeated, a declaration where its parent's block holds none of its
111
+ ; kind, and a node missing what its text needs fail with
112
+ ; TARGET_VALUE_UNREPRESENTABLE, naming what was met.
113
+ ;
114
+ ; The state is a stack of the containers open, innermost last: [:file key
115
+ ; at] for the root, [:body indent parent] for a block's declarations,
116
+ ; [:node indent kind key at begun braced] for a declaration (its line's
117
+ ; indentation, its type, the member whose value comes next, the place of
118
+ ; the last member written, whether its line has begun, whether its block
119
+ ; was written), [:options count] and [:option key at] for an option list,
120
+ ; [:aggregate count] and [:field key at] for an aggregate, [:ranges count]
121
+ ; and [:range key at] for ranges, [:names count] for reserved names, and
122
+ ; :done once the file has closed. Nothing grows with the file's length,
123
+ ; only with its nesting.
124
+
125
+ def proto-render-refuse [message]
126
+ fail :unrepresentable (string-join "" ["the tree is not a proto file: " message])
127
+
128
+ ; The place of a member in a declaration of the kind, or missing where
129
+ ; the kind has no such member.
130
+ def proto-render-place [nk name]
131
+ match [nk name]
132
+ case [k "type"] 0
133
+ case ["syntax" "value"] 1
134
+ case ["edition" "value"] 1
135
+ case ["import" "modifier"] 1
136
+ case ["import" "path"] 2
137
+ case ["package" "name"] 1
138
+ case ["option" "name"] 1
139
+ case ["option" "value"] 2
140
+ case ["message" "visibility"] 1
141
+ case ["message" "name"] 2
142
+ case ["message" "body"] 3
143
+ case ["enum" "visibility"] 1
144
+ case ["enum" "name"] 2
145
+ case ["enum" "body"] 3
146
+ case ["service" "name"] 1
147
+ case ["service" "body"] 2
148
+ case ["oneof" "name"] 1
149
+ case ["oneof" "body"] 2
150
+ case ["extend" "extendee"] 1
151
+ case ["extend" "body"] 2
152
+ case ["field" "label"] 1
153
+ case ["field" "fieldType"] 2
154
+ case ["field" "name"] 3
155
+ case ["field" "number"] 4
156
+ case ["field" "options"] 5
157
+ case ["map" "keyType"] 1
158
+ case ["map" "valueType"] 2
159
+ case ["map" "name"] 3
160
+ case ["map" "number"] 4
161
+ case ["map" "options"] 5
162
+ case ["group" "label"] 1
163
+ case ["group" "name"] 2
164
+ case ["group" "number"] 3
165
+ case ["group" "body"] 4
166
+ case ["extensions" "ranges"] 1
167
+ case ["extensions" "options"] 2
168
+ case ["reserved" "ranges"] 1
169
+ case ["reserved" "names"] 1
170
+ case ["enumValue" "name"] 1
171
+ case ["enumValue" "number"] 2
172
+ case ["enumValue" "options"] 3
173
+ case ["rpc" "name"] 1
174
+ case ["rpc" "inputStream"] 2
175
+ case ["rpc" "inputType"] 3
176
+ case ["rpc" "outputStream"] 4
177
+ case ["rpc" "outputType"] 5
178
+ case ["rpc" "body"] 6
179
+ case _ missing
180
+
181
+ ; The place a declaration's text needs reached: before its block or its
182
+ ; options, and at its end.
183
+ def proto-render-needs [nk]
184
+ match nk
185
+ case "syntax" 1
186
+ case "edition" 1
187
+ case "import" 2
188
+ case "package" 1
189
+ case "option" 2
190
+ case "message" 2
191
+ case "enum" 2
192
+ case "service" 1
193
+ case "oneof" 1
194
+ case "extend" 1
195
+ case "field" 4
196
+ case "map" 4
197
+ case "group" 3
198
+ case "extensions" 1
199
+ case "reserved" 1
200
+ case "enumValue" 2
201
+ case "rpc" 5
202
+ case _ 0
203
+
204
+ ; The member a declaration's text needs before its block or its options,
205
+ ; named in a refusal.
206
+ def proto-render-needed [nk]
207
+ match nk
208
+ case "field" "number"
209
+ case "map" "number"
210
+ case "group" "number"
211
+ case "enumValue" "number"
212
+ case "extend" "extendee"
213
+ case "rpc" "outputType"
214
+ case "extensions" "ranges"
215
+ case "reserved" "ranges or names"
216
+ case "syntax" "value"
217
+ case "edition" "value"
218
+ case "import" "path"
219
+ case "option" "value"
220
+ case _ "name"
221
+
222
+ ; Whether a declaration of the kind holds a block, which its text ends.
223
+ def proto-render-blocked [nk]
224
+ match nk
225
+ case "message" true
226
+ case "enum" true
227
+ case "service" true
228
+ case "oneof" true
229
+ case "extend" true
230
+ case "group" true
239
231
  case _ false
240
232
 
241
- def proto-member [i v]
242
- match (count (filter (fn [x] (proto-same-number x i)) v))
243
- case 0 false
244
- case _ true
245
-
246
- def proto-lines [items]
247
- string-join "\\n" (filter proto-nonempty items)
248
-
249
- def proto-deeper [indent]
250
- string-join "" [indent " "]
251
-
252
- ; A block: its head, then its body on the lines below it, or \`{}\` when the
253
- ; body is empty.
254
- def proto-block [indent head body]
255
- match body
256
- case "" (string-join "" [indent head " {}"])
257
- case _ (string-join "" [indent head " {\\n" body "\\n" indent "}"])
258
-
259
- ; ---- scalars
260
-
261
- ; A number: its lexeme, or as the JSON renderer writes it; NaN, null in
262
- ; JSON, as \`0_0\`, which the reader reads as NaN again.
263
- def proto-num [n]
264
- match (kind n)
265
- case :number
266
- match (number-class n)
267
- case :finite (scalar-text csv-options n)
268
- case :infinity "1e999"
269
- case :negative-infinity "-1e999"
270
- case :nan "0_0"
271
- case :null "0_0"
272
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: a number is not one")
273
-
274
- ; The characters a literal holds as they are between double quotes, and
275
- ; between single quotes: none of the controls, and not that quote.
276
- def proto-no-dq [[32 33] [35 1114111]]
277
-
278
- def proto-no-sq [[32 38] [40 1114111]]
279
-
280
- def proto-ident-chars [[48 57] [65 90] [95 95] [97 122]]
281
-
282
- ; The characters of a bare constant: a number, a signed one, an identifier.
283
- def proto-bare-chars [[43 43] [45 46] [48 57] [65 90] [95 95] [97 122]]
284
-
285
- ; The characters a backtick string holds, as the lexer reads one: the
286
- ; line feed and the carriage return that carry it across lines, and none
287
- ; of the other controls.
288
- def proto-in-backtick [[10 10] [13 13] [32 1114111]]
289
-
290
- ; The characters an aggregate holds: its tokens, and the tab, the line
291
- ; feed and the carriage return as the whitespace between them.
292
- def proto-in-aggregate [[9 10] [13 13] [32 1114111]]
293
-
294
- ; Whether s is a backtick string's text as the reader keeps it, the
295
- ; backticks and all: the lexer reads a backtick string across lines too,
296
- ; a backslash escaping the character after it, and the reader keeps a
297
- ; constant that is not quoted as it was written. So s has a backtick at
298
- ; each end and none unescaped between them, no backslash before the last
299
- ; that would escape it, and no control but a line break.
300
- def proto-backtick [s]
301
- let [parts (split "\`" s)]
302
- match (proto-less 2 (count parts))
303
- case false false
304
- case true
305
- match (match (proto-at parts 0) (case "" (match (top parts) (case "" true) (case _ false))) (case _ false))
306
- case false false
307
- case true
308
- match (chars-within proto-in-backtick s)
309
- case false false
310
- case true (proto-escaped-inside "\`" (proto-after "\`" (string-join "\`" (pop parts))))
311
-
312
- ; Whether text could stand between quotes q as the lexer reads them: every
313
- ; q in it escaped, and no backslash at its end to escape the closing one.
314
- def proto-escaped-inside [q text]
315
- match (proto-quotes-escaped q text)
316
- case false false
317
- case true
318
- match (proto-open-escape text)
319
- case true false
320
- case false true
321
-
322
- ; Whether every quote q in s is escaped, as in the text of a literal
323
- ; between those quotes. The lexer reads an escape as a backslash and the
324
- ; character after it, so an escaped backslash (\`\\\\\`) escapes nothing
325
- ; after it: s is read in the runs between its escaped backslashes, found
326
- ; from the front as the lexer finds them, and in a run a quote is escaped
327
- ; where a backslash comes right before it.
328
- def proto-quotes-escaped [q s]
329
- match (count (filter (fn [run] (proto-bare-quote q run)) (split "\\\\\\\\" s)))
330
- case 0 true
233
+ ; Whether a block of the parent's kind holds a declaration of the kind.
234
+ def proto-render-holds [parent nk]
235
+ match [parent nk]
236
+ case ["file" "syntax"] true
237
+ case ["file" "edition"] true
238
+ case ["file" "import"] true
239
+ case ["file" "package"] true
240
+ case ["file" "option"] true
241
+ case ["file" "message"] true
242
+ case ["file" "enum"] true
243
+ case ["file" "service"] true
244
+ case ["file" "extend"] true
245
+ case ["message" k] (proto-render-in-message k)
246
+ case ["group" k] (proto-render-in-message k)
247
+ case ["enum" "option"] true
248
+ case ["enum" "reserved"] true
249
+ case ["enum" "enumValue"] true
250
+ case ["service" "option"] true
251
+ case ["service" "rpc"] true
252
+ case ["rpc" "option"] true
253
+ case ["oneof" "option"] true
254
+ case ["oneof" "field"] true
255
+ case ["oneof" "group"] true
256
+ case ["extend" "field"] true
331
257
  case _ false
332
258
 
333
- ; Whether a run of a literal's text that holds no escaped backslash holds
334
- ; the quote q with no backslash right before it.
335
- def proto-bare-quote [q run]
336
- match (count (filter (fn [piece] (proto-less 1 (count (split q piece)))) (split (string-join "" ["\\\\" q]) run)))
337
- case 0 false
338
- case _ true
339
-
340
- ; Whether s ends with a backslash that escapes nothing in it, which would
341
- ; escape the quote written after it: its last run between escaped
342
- ; backslashes ends with one.
343
- def proto-open-escape [s]
344
- let [pieces (split "\\\\" (top (split "\\\\\\\\" s)))]
345
- match (count pieces)
346
- case 1 false
347
- case _
348
- match (top pieces)
349
- case "" true
350
- case _ false
351
-
352
- ; A literal's text between quotes that hold it, or "" where none do:
353
- ; double quotes where it holds no double quote, single quotes where it
354
- ; holds no single quote, and where it holds both, the quotes it escapes.
355
- ; No quotes hold a control character, both quotes unescaped, or a
356
- ; backslash at the end that would escape the closing quote.
357
- def proto-quoted [s]
358
- match (chars-within [[32 1114111]] s)
359
- case false ""
360
- case true
361
- match (proto-open-escape s)
362
- case true ""
363
- case false
364
- match (chars-within proto-no-dq s)
365
- case true (string-join "" ["\\"" s "\\""])
366
- case false
367
- match (chars-within proto-no-sq s)
368
- case true (string-join "" ["'" s "'"])
369
- case false
370
- match (proto-quotes-escaped "\\"" s)
371
- case true (string-join "" ["\\"" s "\\""])
372
- case false
373
- match (proto-quotes-escaped "'" s)
374
- case true (string-join "" ["'" s "'"])
375
- case false ""
376
-
377
- ; A string literal whose text is s, as written: between quotes, or as the
378
- ; backtick string it is, which holds a line break no quotes hold. A text
379
- ; neither holds fails.
380
- def proto-literal [s]
381
- match (proto-quoted s)
382
- case ""
383
- match (proto-backtick s)
384
- case true s
385
- case false (fail :unrepresentable "a string holds what no literal or backtick string holds: a control character, both quotes unescaped, or a backslash that would escape the closing quote")
386
- case q q
387
-
388
- ; An option's string: a literal, the backtick string it is, or the
389
- ; aggregate it is, whose braces hold what no literal holds (a line break,
390
- ; or both quotes unescaped). A control that no literal, backtick string
391
- ; or aggregate holds fails.
392
- def proto-option-string [s]
393
- match (proto-quoted s)
394
- case ""
395
- match (proto-backtick s)
396
- case true s
397
- case false
398
- match (chars-within proto-in-aggregate s)
399
- case true (string-join "" ["{" s "}"])
400
- case false (fail :unrepresentable "an option's value holds a control character that no literal, backtick string or aggregate holds")
401
- case q q
402
-
403
- ; The options descriptor.proto types as enums, whose values are identifiers.
404
- def proto-enum-option [k]
405
- match k
406
- case "optimize_for" true
407
- case "ctype" true
408
- case "jstype" true
409
- case "retention" true
410
- case "targets" true
411
- case "idempotency_level" true
412
- case "features.field_presence" true
413
- case "features.enum_type" true
414
- case "features.repeated_field_encoding" true
415
- case "features.utf8_validation" true
416
- case "features.message_encoding" true
417
- case "features.json_format" true
418
- case "features.enforce_naming_style" true
419
- case "features.default_symbol_visibility" true
420
- case "features.(pb.cpp).string_type" true
421
- case "features.(pb.java).utf8_validation" true
422
- case "features.(pb.java).nest_in_file_class" true
423
- case "features.(pb.go).api_level" true
424
- case "features.(pb.go).strip_enum_prefix" true
259
+ def proto-render-in-message [nk]
260
+ match nk
261
+ case "option" true
262
+ case "field" true
263
+ case "map" true
264
+ case "group" true
265
+ case "oneof" true
266
+ case "message" true
267
+ case "enum" true
268
+ case "extend" true
269
+ case "extensions" true
270
+ case "reserved" true
425
271
  case _ false
426
272
 
427
- def proto-identifier [s]
428
- match s
429
- case "" false
430
- case "true" false
431
- case "false" false
432
- case _ (chars-within proto-ident-chars s)
433
-
434
- def proto-value [k v]
435
- match (kind v)
436
- case :boolean
437
- match v
438
- case true "true"
439
- case false "false"
440
- case :number (proto-num v)
441
- case :null "1e999"
442
- case :string
443
- match (proto-enum-option k)
444
- case true
445
- match (proto-identifier v)
446
- case true v
447
- case false (proto-option-string v)
448
- case false (proto-option-string v)
449
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: an option's value is not a string, a number or a boolean")
450
-
451
- ; ---- options
452
-
453
- def proto-option-lines [indent opts]
454
- string-join "\\n"
455
- map
456
- fn [k]
457
- string-join "" [indent "option " k " = " (proto-value k (get k opts)) ";"]
458
- filter proto-real-key (keys opts)
459
-
460
- def proto-option-items [opts]
461
- string-join ", "
462
- map
463
- fn [k]
464
- string-join "" [k " = " (proto-value k (get k opts))]
465
- filter proto-real-key (keys opts)
466
-
467
- def proto-brackets [items]
468
- match items
469
- case "" ""
470
- case _ (string-join "" [" [" items "]"])
471
-
472
- def proto-plain-options [o]
473
- proto-brackets (proto-option-items (proto-record "options" o))
474
-
475
- ; ---- fields
476
-
477
- def proto-default [f]
478
- let [v (proto-text "a field's default" "defaultValue" f)]
479
- match (get "type" f)
480
- case "TYPE_STRING" (proto-literal v)
481
- case "TYPE_BYTES" (proto-literal v)
482
- case _
483
- match v
484
- case "" (proto-literal v)
485
- case _
486
- match (chars-within proto-bare-chars v)
487
- case true v
488
- case false (proto-literal v)
489
-
490
- def proto-field-options [f]
491
- proto-brackets
492
- string-join ", "
493
- filter proto-nonempty
494
- vector
495
- match (proto-has "defaultValue" f)
496
- case true (string-join "" ["default = " (proto-default f)])
497
- case false ""
498
- match (proto-has "jsonName" f)
499
- case true (string-join "" ["json_name = " (proto-literal (proto-text "a field" "jsonName" f))])
500
- case false ""
501
- proto-option-items (proto-record "options" f)
502
-
503
- def proto-scalar-type [t]
504
- match t
505
- case "TYPE_DOUBLE" "double"
506
- case "TYPE_FLOAT" "float"
507
- case "TYPE_INT64" "int64"
508
- case "TYPE_UINT64" "uint64"
509
- case "TYPE_INT32" "int32"
510
- case "TYPE_FIXED64" "fixed64"
511
- case "TYPE_FIXED32" "fixed32"
512
- case "TYPE_BOOL" "bool"
513
- case "TYPE_STRING" "string"
514
- case "TYPE_BYTES" "bytes"
515
- case "TYPE_UINT32" "uint32"
516
- case "TYPE_SFIXED32" "sfixed32"
517
- case "TYPE_SFIXED64" "sfixed64"
518
- case "TYPE_SINT32" "sint32"
519
- case "TYPE_SINT64" "sint64"
520
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: a field's type is no scalar a .proto file names")
521
-
522
- def proto-type [f]
523
- match (proto-has "type" f)
524
- case true (proto-scalar-type (get "type" f))
525
- case false (proto-text "a field" "typeName" f)
526
-
527
- def proto-label [syntax f]
528
- match (get "label" f)
529
- case "LABEL_REQUIRED" "required "
530
- case "LABEL_REPEATED" "repeated "
531
- case _
532
- match (get "proto3Optional" f)
533
- case true "optional "
273
+ ; The text a member's scalar writes in a declaration of the kind, its
274
+ ; line's indentation ahead of it where the line has not begun.
275
+ def proto-render-member-text [nk name value]
276
+ match [nk name]
277
+ case ["syntax" "value"] (string-join "" ["syntax = " value])
278
+ case ["edition" "value"] (string-join "" ["edition = " value])
279
+ case ["import" "modifier"] (string-join "" ["import " value " "])
280
+ case ["package" "name"] (string-join "" ["package " value])
281
+ case ["option" "name"] (string-join "" ["option " value " = "])
282
+ case ["option" "value"] value
283
+ case ["message" "visibility"] (string-join "" [value " "])
284
+ case ["message" "name"] (string-join "" ["message " value])
285
+ case ["enum" "visibility"] (string-join "" [value " "])
286
+ case ["enum" "name"] (string-join "" ["enum " value])
287
+ case ["service" "name"] (string-join "" ["service " value])
288
+ case ["oneof" "name"] (string-join "" ["oneof " value])
289
+ case ["extend" "extendee"] (string-join "" ["extend " value])
290
+ case ["group" "label"] (string-join "" [value " "])
291
+ case ["group" "name"] (string-join "" ["group " value])
292
+ case ["group" "number"] (string-join "" [" = " value])
293
+ case ["field" "label"] (string-join "" [value " "])
294
+ case ["field" "fieldType"] (string-join "" [value " "])
295
+ case ["field" "name"] value
296
+ case ["field" "number"] (string-join "" [" = " value])
297
+ case ["map" "keyType"] (string-join "" ["map<" value])
298
+ case ["map" "valueType"] (string-join "" [", " value "> "])
299
+ case ["map" "name"] value
300
+ case ["map" "number"] (string-join "" [" = " value])
301
+ case ["enumValue" "name"] value
302
+ case ["enumValue" "number"] (string-join "" [" = " value])
303
+ case ["rpc" "name"] (string-join "" ["rpc " value " ("])
304
+ case ["rpc" "inputStream"] "stream "
305
+ case ["rpc" "inputType"] (string-join "" [value ") returns ("])
306
+ case ["rpc" "outputStream"] "stream "
307
+ case ["rpc" "outputType"] (string-join "" [value ")"])
308
+
309
+ ; An import's path, which begins the line where no modifier did.
310
+ def proto-render-path [begun value]
311
+ match begun
312
+ case true value
313
+ case _ (string-join "" ["import " value])
314
+
315
+ ; The stack with its innermost container replaced.
316
+ def proto-render-put [stack ctx]
317
+ push ctx (pop stack)
318
+
319
+ ; A scalar's value, checked: a string, or \`true\` for an rpc's stream.
320
+ def proto-render-value [nk name value]
321
+ match [name (kind value)]
322
+ case ["inputStream" :boolean] (if value value (proto-render-refuse "an rpc's inputStream is true or left out"))
323
+ case ["outputStream" :boolean] (if value value (proto-render-refuse "an rpc's outputStream is true or left out"))
324
+ case ["inputStream" k] (proto-render-refuse "an rpc's inputStream is true or left out")
325
+ case ["outputStream" k] (proto-render-refuse "an rpc's outputStream is true or left out")
326
+ case [n :string] value
327
+ case _ (proto-render-refuse (string-join "" ["a " nk "'s " name " is text"]))
328
+
329
+ ; A declaration's member is about to be written: its place, checked.
330
+ def proto-render-placed [nk name at]
331
+ match (proto-render-place nk name)
332
+ case missing (proto-render-refuse (string-join "" ["a " nk " has no member " name]))
333
+ case place
334
+ match (compare place at)
335
+ case :greater place
336
+ case _ (proto-render-refuse (string-join "" ["a " nk "'s " name " comes after what it precedes, or twice"]))
337
+
338
+ ; A declaration's type is read: its kind, held to its parent's block.
339
+ def proto-render-type [stack ctx value]
340
+ match ctx
341
+ case [:node indent nk key at begun braced]
342
+ match (top (pop stack))
343
+ case [:body i parent]
344
+ match (kind value)
345
+ case :string
346
+ match (proto-render-holds parent value)
347
+ case true (transition (proto-render-put stack [:node indent value "" 0 false false]) [])
348
+ case _ (proto-render-refuse (string-join "" ["a " parent "'s block holds no " value]))
349
+ case _ (proto-render-refuse "a declaration's type is text")
350
+
351
+ ; A scalar member of a declaration.
352
+ def proto-render-node-scalar [stack ctx value]
353
+ match ctx
354
+ case [:node indent nk "type" at begun braced] (proto-render-type stack ctx value)
355
+ case [:node indent "" key at begun braced] (proto-render-refuse "a declaration's first member is not its type")
356
+ case [:node indent nk key at begun braced]
357
+ let [place (proto-render-placed nk key at) checked (proto-render-value nk key value)]
358
+ let [text (match [nk key] (case ["import" "path"] (proto-render-path begun checked)) (case _ (proto-render-member-text nk key checked)))]
359
+ transition (proto-render-put stack [:node indent nk "" place true braced]) [(if begun text (string-join "" [indent text]))]
360
+
361
+ ; A container member of a declaration begins: a block, options in
362
+ ; brackets, ranges, names, or an option's aggregate.
363
+ def proto-render-node-array [stack ctx]
364
+ match ctx
365
+ case [:node indent "" key at begun braced] (proto-render-refuse "a declaration's first member is not its type")
366
+ case [:node indent nk key at begun braced]
367
+ let [place (proto-render-placed nk key at)]
368
+ match key
369
+ case "body"
370
+ match (compare at (proto-render-needs nk))
371
+ case :less (proto-render-refuse (string-join "" ["a " nk " has a block and no " (proto-render-needed nk)]))
372
+ case _ (transition (push [:body (string-join "" [indent " "]) nk] (proto-render-put stack [:node indent nk "" place true true])) [" {\\n"])
373
+ case "options"
374
+ match (compare at (proto-render-needs nk))
375
+ case :less (proto-render-refuse (string-join "" ["a " nk " has options and no " (proto-render-needed nk)]))
376
+ case _ (transition (push [:options 0] (proto-render-put stack [:node indent nk "" place true braced])) [" ["])
377
+ case "ranges" (transition (push [:ranges 0] (proto-render-put stack [:node indent nk "" place true braced])) [(string-join "" [indent nk " "])])
378
+ case "names" (transition (push [:names 0] (proto-render-put stack [:node indent nk "" place true braced])) [(string-join "" [indent nk " "])])
379
+ case "value"
380
+ match nk
381
+ case "option" (transition (push [:aggregate 0] (proto-render-put stack [:node indent nk "" place true braced])) ["{"])
382
+ case _ (proto-render-refuse (string-join "" ["a " nk "'s value is text"]))
383
+ case _ (proto-render-refuse (string-join "" ["a " nk "'s " key " is text"]))
384
+
385
+ ; A declaration ends: its terminator, once its text holds what it needs.
386
+ def proto-render-node-end [stack ctx]
387
+ match ctx
388
+ case [:node indent nk key at begun braced]
389
+ match (compare at (proto-render-needs nk))
390
+ case :less (proto-render-refuse (string-join "" ["a " nk " with no " (proto-render-needed nk)]))
534
391
  case _
535
- match syntax
536
- case "proto2" "optional "
537
- case _ ""
538
-
539
- def proto-field-line [syntax indent f labelled]
540
- string-join ""
541
- vector
542
- indent
543
- match labelled
544
- case true (proto-label syntax f)
545
- case false ""
546
- proto-type f
547
- " "
548
- proto-name "a field" f
549
- " = "
550
- proto-num (get "number" f)
551
- proto-field-options f
552
- ";"
553
-
554
- def proto-visibility [o]
555
- match (get "visibility" o)
556
- case "VISIBILITY_EXPORT" "export "
557
- case "VISIBILITY_LOCAL" "local "
558
- case _ ""
559
-
560
- ; ---- ranges
561
-
562
- ; The decimal digits, and the places of a number's text.
563
- def proto-digits-down ["9" "8" "7" "6" "5" "4" "3" "2" "1"]
564
-
565
- def proto-digits-up ["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"]
566
-
567
- 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]
568
-
569
- def proto-places-of [text]
570
- filter (fn [i] (proto-less i (length text))) proto-places
571
-
572
- ; A digit after a prefix, a leading zero dropped.
573
- def proto-append-digit [prefix d]
574
- match [prefix d]
575
- case ["" "0"] ""
576
- case _ (string-join "" [prefix d])
577
-
578
- ; The decimal text of n - 1 for an integer n of at least 1, a digit a
579
- ; place from the left: at each place, the largest digit after which some
580
- ; number is still below n, which is the one whose zeros after it are.
581
- ; alchemy has no arithmetic, so the predecessor is found by comparison.
582
- def proto-pred-pos [self places prefix n]
583
- match (count places)
584
- case 0
585
- match prefix
586
- case "" "0"
587
- case _ prefix
392
+ match [(proto-render-blocked nk) braced]
393
+ case [true true] (transition (pop stack) ["\\n"])
394
+ case [true false] (proto-render-refuse (string-join "" ["a " nk " with no body"]))
395
+ case [false true] (transition (pop stack) ["\\n"])
396
+ case _ (transition (pop stack) [";\\n"])
397
+
398
+ ; A container ends: a block's closing brace, on a line of its own; an
399
+ ; option list's bracket; an aggregate's brace.
400
+ def proto-render-array-end [stack]
401
+ match (top stack)
402
+ case [:body indent parent]
403
+ let [rest (pop stack)]
404
+ match (top rest)
405
+ case [:node i nk key at begun braced] (transition rest [(string-join "" [i "}"])])
406
+ case _ (transition rest [])
407
+ case [:options count] (transition (pop stack) ["]"])
408
+ case [:aggregate count] (transition (pop stack) [" }"])
409
+ case [:ranges count] (transition (pop stack) [])
410
+ case [:names count] (transition (pop stack) [])
411
+ case _ (proto-render-refuse "an array where none is")
412
+
413
+ def proto-render-object-start [stack]
414
+ match (count stack)
415
+ case 0 (transition [[:file "" 0]] [])
588
416
  case _
589
- let [zeros (repeat (count (pop places)) "0")]
590
- let [found (filter (fn [d] (proto-less (number (string-join "" [prefix d zeros])) n)) proto-digits-down)]
591
- match (count found)
592
- case 0 (self self (pop places) (proto-append-digit prefix "0") n)
593
- case _ (self self (pop places) (proto-append-digit prefix (proto-at found 0)) n)
594
-
595
- def proto-below-negated [prefix d nines n]
596
- let [digits (string-join "" [(proto-append-digit prefix d) nines])]
597
- match digits
598
- case "" false
599
- case _ (proto-less (number (string-join "" ["-" digits])) n)
600
-
601
- ; The same for a negative n: the smallest m with -m below n, a digit a
602
- ; place from the left, the smallest digit after which nines still are.
603
- def proto-pred-neg [self places prefix n]
604
- match (count places)
605
- case 0 (string-join "" ["-" prefix])
417
+ match (top stack)
418
+ case [:body indent parent] (transition (push [:node indent "" "type" -1 false false] stack) [])
419
+ case [:options count] (transition (push [:option "" 0] (proto-render-put stack [:options (inc count)])) [(if (equal count 0) "" ", ")])
420
+ case [:aggregate count] (transition (push [:field "" 0] (proto-render-put stack [:aggregate (inc count)])) [" "])
421
+ case [:ranges count] (transition (push [:range "" 0] (proto-render-put stack [:ranges (inc count)])) [(if (equal count 0) "" ", ")])
422
+ case _ (proto-render-refuse "an object where none is")
423
+
424
+ def proto-render-object-end [stack]
425
+ match (top stack)
426
+ case [:file key at]
427
+ match at
428
+ case 2 (transition [:done] [])
429
+ case _ (proto-render-refuse "a file with no body")
430
+ case [:node indent nk key at begun braced] (proto-render-node-end stack (top stack))
431
+ case [:option key at]
432
+ match at
433
+ case 2 (transition (pop stack) [])
434
+ case _ (proto-render-refuse "an option with no name or no value")
435
+ case [:field key at]
436
+ match at
437
+ case 2 (transition (pop stack) [])
438
+ case _ (proto-render-refuse "an aggregate's field with no name or no value")
439
+ case [:range key at]
440
+ match (compare at 1)
441
+ case :less (proto-render-refuse "a range with no from")
442
+ case _ (transition (pop stack) [])
443
+ case _ (proto-render-refuse "an object's end where none is open")
444
+
445
+ def proto-render-array-start [stack]
446
+ match (top stack)
447
+ case [:file "body" 1] (transition (push [:body "" "file"] (proto-render-put stack [:file "" 2])) [])
448
+ case [:node indent nk key at begun braced] (proto-render-node-array stack (top stack))
449
+ case [:option "value" 1] (transition (push [:aggregate 0] (proto-render-put stack [:option "" 2])) ["{"])
450
+ case [:field "value" 1] (transition (push [:aggregate 0] (proto-render-put stack [:field "" 2])) [" {"])
451
+ case _ (proto-render-refuse "an array where none is")
452
+
453
+ ; A key names the member whose value follows.
454
+ def proto-render-key [stack name]
455
+ match (top stack)
456
+ case [:file key at] (transition (proto-render-put stack [:file name at]) [])
457
+ case [:node indent nk key at begun braced] (transition (proto-render-put stack [:node indent nk name at begun braced]) [])
458
+ case [:option key at] (transition (proto-render-put stack [:option name at]) [])
459
+ case [:field key at] (transition (proto-render-put stack [:field name at]) [])
460
+ case [:range key at] (transition (proto-render-put stack [:range name at]) [])
461
+ case _ (proto-render-refuse "a key outside an object")
462
+
463
+ ; A text, checked.
464
+ def proto-render-text [value what]
465
+ match (kind value)
466
+ case :string value
467
+ case _ (proto-render-refuse (string-join "" [what " is text"]))
468
+
469
+ def proto-render-scalar [stack value]
470
+ match (top stack)
471
+ case [:file "type" 0]
472
+ match value
473
+ case "file" (transition (proto-render-put stack [:file "" 1]) [])
474
+ case _ (proto-render-refuse "the root's type is not file")
475
+ case [:node indent nk key at begun braced] (proto-render-node-scalar stack (top stack) value)
476
+ case [:option "name" 0] (transition (proto-render-put stack [:option "" 1]) [(string-join "" [(proto-render-text value "an option's name") " = "])])
477
+ case [:option "value" 1] (transition (proto-render-put stack [:option "" 2]) [(proto-render-text value "an option's value")])
478
+ case [:field "name" 0] (transition (proto-render-put stack [:field "" 1]) [(proto-render-text value "an aggregate field's name")])
479
+ case [:field "value" 1] (transition (proto-render-put stack [:field "" 2]) [(string-join "" [": " (proto-render-text value "an aggregate field's value")])])
480
+ case [:range "from" 0] (transition (proto-render-put stack [:range "" 1]) [(proto-render-text value "a range's from")])
481
+ case [:range "to" 1] (transition (proto-render-put stack [:range "" 2]) [(string-join "" [" to " (proto-render-text value "a range's to")])])
482
+ case [:names count] (transition (proto-render-put stack [:names (inc count)]) [(string-join "" [(if (equal count 0) "" ", ") (proto-render-text value "a reserved name")])])
483
+ case _ (proto-render-refuse "a value where none is, or a member out of its place")
484
+
485
+ def proto-render-step [stack event]
486
+ match (count stack)
487
+ case 0
488
+ match event
489
+ case object-start (proto-render-object-start stack)
490
+ case _ (proto-render-refuse "the root is not an object")
606
491
  case _
607
- let [nines (repeat (count (pop places)) "9")]
608
- let [found (filter (fn [d] (proto-below-negated prefix d nines n)) proto-digits-up)]
609
- self self (pop places) (proto-append-digit prefix (proto-at found 0)) n
610
-
611
- def proto-pred [n]
612
- match (compare n 0)
613
- case :greater (proto-pred-pos proto-pred-pos (proto-places-of (proto-num n)) "" n)
614
- case :equal "-1"
615
- case _ (proto-pred-neg proto-pred-neg (proto-places-of (proto-num n)) "" n)
616
-
617
- ; A message's range, whose end is exclusive: \`start\`, \`start to last\` or
618
- ; \`start to max\`.
619
- def proto-range-ex [maxv r]
620
- let [s (get "start" r)]
621
- let [e (get "end" r)]
622
- match (compare e maxv)
623
- case :equal (string-join "" [(proto-num s) " to max"])
624
- case _
625
- let [p (proto-pred e)]
626
- match (compare (number p) s)
627
- case :equal (proto-num s)
628
- case _ (string-join "" [(proto-num s) " to " p])
629
-
630
- ; An enum's reserved range, whose end is inclusive.
631
- def proto-range-in [r]
632
- let [s (get "start" r)]
633
- let [e (get "end" r)]
634
- match (compare e s)
635
- case :equal (proto-num s)
492
+ match (top stack)
493
+ case :done (fail :protocol-order "the events hold more after the file, which a tree's never do")
636
494
  case _
637
- match (compare e 2147483647)
638
- case :equal (string-join "" [(proto-num s) " to max"])
639
- case _ (string-join "" [(proto-num s) " to " (proto-num e)])
640
-
641
- ; The end \`to max\` gives in a message.
642
- def proto-max [m]
643
- match (get "message_set_wire_format" (proto-record "options" m))
644
- case true 2147483647
645
- case _ 536870912
646
-
647
- ; A reserved name: an identifier in an editions file, a literal before.
648
- def proto-reserved-name [syntax n]
649
- match syntax
650
- case "editions"
651
- match (proto-identifier n)
652
- case true n
653
- case false (proto-name-literal n)
654
- case _ (proto-name-literal n)
655
-
656
- ; A reserved name between quotes. The reader takes a reserved name from a
657
- ; quoted literal or an identifier, and reads the words of a backtick
658
- ; string as names of their own, so a name no quotes hold fails.
659
- def proto-name-literal [n]
660
- match (proto-quoted n)
661
- case "" (fail :unrepresentable "a reserved name holds what no quotes hold: a control character, both quotes unescaped, or a backslash that would escape the closing quote")
662
- case q q
663
-
664
- def proto-reserved-names [syntax indent names]
665
- match (count names)
666
- case 0 ""
667
- case _ (string-join "" [indent "reserved " (string-join ", " (map (fn [n] (proto-reserved-name syntax n)) names)) ";"])
668
-
669
- def proto-reserved-ranges [indent text]
670
- match text
671
- case "" ""
672
- case _ (string-join "" [indent "reserved " text ";"])
673
-
674
- ; ---- messages
675
-
676
- ; Whether two strings are the same. alchemy matches a string against a
677
- ; literal and against no other string, but \`split\` cuts a string at every
678
- ; occurrence of a separator that is not empty, so a string cut by itself,
679
- ; and by nothing else, is two empty strings.
680
- def proto-same-text [a b]
681
- match a
682
- case ""
683
- match b
684
- case "" true
685
- case _ false
686
- case _
687
- match (split a b)
688
- case ["" ""] true
689
- case _ false
690
-
691
- ; Whether v is a string and the string s.
692
- def proto-is-text [s v]
693
- match (kind v)
694
- case :string (proto-same-text s v)
495
+ match event
496
+ case object-start (proto-render-object-start stack)
497
+ case object-end (proto-render-object-end stack)
498
+ case array-start (proto-render-array-start stack)
499
+ case array-end (proto-render-array-end stack)
500
+ case (key name) (proto-render-key stack name)
501
+ case (scalar value) (proto-render-scalar stack value)
502
+
503
+ def proto-render-finish [stack]
504
+ match stack
505
+ case [:done] []
506
+ case [] (fail :protocol-order "the events hold no value, where a tree's hold one")
507
+ case _ (fail :protocol-order "the events ended inside a container, which a tree's never do")
508
+
509
+ ; The render: the tree's events in, the \`.proto\` file's text out.
510
+ def proto-render [input]
511
+ join ""
512
+ scan-emit [] proto-render-step proto-render-finish (events input)
513
+ ` }),
514
+ transduce: Object.freeze({ entry: "proto-transduce", source: `; proto's transducer part: the source-ordered tree's JSON events from the
515
+ ; proto grammar's rule passes, for a host that reads parser events (dmog's
516
+ ; \`translate\` and \`run --grammar proto\`). Every event is the one the walk
517
+ ; of the tree \`parseTree\` builds gives (the schema \`proto-ast\`, alchemy's
518
+ ; docs/streaming-plan.md section 13.1): the file's declarations in source
519
+ ; order, each an object whose \`type\` comes first and whose other members
520
+ ; follow in the order the statement spells them, and every scalar the
521
+ ; text the source spells, its tokens run together. It is hand-written,
522
+ ; since the grammar is compiled from proto-grammar/*.abnf and builds a
523
+ ; \`{rule, src, kids}\` node per rule, which the passes do not show.
524
+ ;
525
+ ; The passes. The compiled grammar names a statement's alternative after
526
+ ; the rule that dispatches it, and the dispatch pushes it: \`topLevelDef$alt4\`
527
+ ; is a message, \`messageElement$alt12\` a field (\`proto-transduce-statement\`
528
+ ; has the table, which proto-grammar/*.abnf's alternative order fixes, and
529
+ ; each runtime's tests hold it to the keyword each alternative opens with).
530
+ ; A statement's object begins at its alternative's open pass and ends at
531
+ ; the close of the last rule its alternative is replaced with (\`$step1\`,
532
+ ; \`$step2\`, ...), so a statement is a chain of rules that one marker
533
+ ; spans: a close whose alternate replaces its rule hands its marker on to
534
+ ; the rule that replaces it, as an open replaced at once does. An extend's
535
+ ; field (\`field\`), an rpc block's option (\`optionStmt\`), an option in
536
+ ; brackets (\`fieldOption\`) and an aggregate's field (\`messageValueEntry\`)
537
+ ; are rules of their own, and so is each statement's value: a member is
538
+ ; the text of the rule that holds it (\`ident\` a name, \`fieldNumber\` a
539
+ ; number, \`fieldType\` a type, \`optionName\` an option's name, \`constant\` a
540
+ ; value, \`strLit\` a path), every token the passes between its open and its
541
+ ; close consumed, run together, as the grammar's node keeps its \`src\`.
542
+ ; What the grammar inlines into the rule that leads to it is read from the
543
+ ; tokens: an import's modifier, a message's or an enum's visibility, an
544
+ ; rpc's \`stream\`, a body's braces, an enum value's name and its signed
545
+ ; number, the name of an option in brackets (up to its \`=\`), the bounds of
546
+ ; a reserved or an extensions range, a reserved name, an aggregate field's
547
+ ; name and the string literals it runs together. An aggregate (a constant
548
+ ; whose alternative is \`constant$alt5\`) is its fields, an array.
549
+ ;
550
+ ; The state is [started marks frames text carry]: whether any pass came
551
+ ; (a source that makes none, the empty file, is an empty file at finish);
552
+ ; a marker per open rule whose close will come, innermost last (:none,
553
+ ; :text, :array, :statement, :file); a frame per open statement, [kind
554
+ ; phase], the phase saying what its next token is; the text being read,
555
+ ; [string mode] or missing, at most one at a time, whose mode says what
556
+ ; ends it (:rule, its rule's close; :token, a token its frame reads as an
557
+ ; end, or another member); and the marker a replaced rule hands on, or
558
+ ; missing. Nothing grows with the document's length, only with its
559
+ ; nesting and the text of one member, under max_metadata_bytes.
560
+
561
+ ; The kind of statement a rule begins, by its name, or missing.
562
+ def proto-transduce-statement [name]
563
+ match (first-split "$alt" name)
564
+ case ["syntaxOrEdition" n]
565
+ match n
566
+ case "0" :syntax
567
+ case "1" :edition
568
+ case _ missing
569
+ case ["topLevelDef" n]
570
+ match n
571
+ case "0" :import
572
+ case "1" :import
573
+ case "2" :package
574
+ case "3" :option
575
+ case "4" :message
576
+ case "5" :enum
577
+ case "6" :service
578
+ case "7" :extend
579
+ case "8" :empty
580
+ case "9" :visible-message
581
+ case "10" :visible-enum
582
+ case _ missing
583
+ case ["messageElement" n]
584
+ match n
585
+ case "0" :enum
586
+ case "1" :message
587
+ case "2" :option
588
+ case "3" :oneof
589
+ case "4" :map
590
+ case "5" :extend
591
+ case "6" :extensions
592
+ case "7" :reserved
593
+ case "8" :empty
594
+ case "9" :visible-message
595
+ case "10" :visible-enum
596
+ case "11" :group
597
+ case "12" :field
598
+ case _ missing
599
+ case ["enumElement" n]
600
+ match n
601
+ case "0" :option
602
+ case "1" :reserved
603
+ case "2" :empty
604
+ case "3" :enum-value
605
+ case _ missing
606
+ case ["oneofElement" n]
607
+ match n
608
+ case "0" :option
609
+ case "1" :empty
610
+ case "2" :group
611
+ case "3" :oneof-field
612
+ case _ missing
613
+ case ["serviceElement" n]
614
+ match n
615
+ case "0" :option
616
+ case "1" :empty
617
+ case "2" :rpc
618
+ case _ missing
619
+ case ["field"] :field
620
+ case ["optionStmt"] :option
621
+ case ["fieldOption"] :field-option
622
+ case ["messageValueEntry"] :entry
623
+ case _ missing
624
+
625
+ ; The \`type\` a statement's object holds.
626
+ def proto-transduce-type [kind]
627
+ match kind
628
+ case :visible-message "message"
629
+ case :visible-enum "enum"
630
+ case :oneof-field "field"
631
+ case :enum-value "enumValue"
632
+ case :syntax "syntax"
633
+ case :edition "edition"
634
+ case :import "import"
635
+ case :package "package"
636
+ case :option "option"
637
+ case :message "message"
638
+ case :enum "enum"
639
+ case :service "service"
640
+ case :extend "extend"
641
+ case :oneof "oneof"
642
+ case :map "map"
643
+ case :extensions "extensions"
644
+ case :reserved "reserved"
645
+ case :group "group"
646
+ case :field "field"
647
+ case :rpc "rpc"
648
+
649
+ ; Whether a statement of the kind holds a body in braces.
650
+ def proto-transduce-has-body [kind]
651
+ match kind
652
+ case :message true
653
+ case :visible-message true
654
+ case :enum true
655
+ case :visible-enum true
656
+ case :service true
657
+ case :oneof true
658
+ case :extend true
659
+ case :group true
660
+ case :rpc true
695
661
  case _ false
696
662
 
697
- ; Whether the string p begins with the string c, which is not empty, and p
698
- ; without that first c.
699
- def proto-begins [c p]
700
- let [parts (split c p)]
701
- match (proto-at parts 0)
702
- case "" (proto-less 1 (count parts))
663
+ ; The member a rule's text is in a statement of the kind: a key, :type for
664
+ ; a map's or an rpc's type (the first or the second), :strings for the
665
+ ; first string literal of an aggregate field's run, :options for an
666
+ ; option list, or missing where the rule is no member of it.
667
+ def proto-transduce-member [kind name]
668
+ match [kind name]
669
+ case [:syntax "strLit"] "value"
670
+ case [:edition "strLit"] "value"
671
+ case [:import "strLit"] "path"
672
+ case [:entry "strLit"] :strings
673
+ case [:package "fullIdent"] "name"
674
+ case [:oneof-field "fullIdent"] "fieldType"
675
+ case [:option "optionName"] "name"
676
+ case [:option "constant"] "value"
677
+ case [:field-option "constant"] "value"
678
+ case [:entry "constant"] "value"
679
+ case [:message "ident"] "name"
680
+ case [:visible-message "ident"] "name"
681
+ case [:enum "ident"] "name"
682
+ case [:visible-enum "ident"] "name"
683
+ case [:service "ident"] "name"
684
+ case [:oneof "ident"] "name"
685
+ case [:group "ident"] "name"
686
+ case [:field "ident"] "name"
687
+ case [:oneof-field "ident"] "name"
688
+ case [:map "ident"] "name"
689
+ case [:rpc "ident"] "name"
690
+ case [:group "fieldNumber"] "number"
691
+ case [:field "fieldNumber"] "number"
692
+ case [:oneof-field "fieldNumber"] "number"
693
+ case [:map "fieldNumber"] "number"
694
+ case [:field "label"] "label"
695
+ case [:group "label"] "label"
696
+ case [:field "fieldType"] "fieldType"
697
+ case [:map "fieldType"] :type
698
+ case [:extend "messageType"] "extendee"
699
+ case [:rpc "messageType"] :type
700
+ case [:field "fieldOptions"] :options
701
+ case [:oneof-field "fieldOptions"] :options
702
+ case [:map "fieldOptions"] :options
703
+ case [:extensions "fieldOptions"] :options
704
+ case [:enum-value "fieldOptions"] :options
705
+ case _ missing
706
+
707
+ ; The machine a step threads: [marks frames text carry outputs].
708
+ def proto-transduce-emit [m more]
709
+ match m
710
+ case [marks frames text carry outputs] [marks frames text carry (push-all more outputs)]
711
+
712
+ def proto-transduce-frame [m]
713
+ match m
714
+ case [marks frames text carry outputs]
715
+ match (count frames)
716
+ case 0 missing
717
+ case _ (top frames)
718
+
719
+ ; The innermost frame in a new phase.
720
+ def proto-transduce-phase [m phase]
721
+ match m
722
+ case [marks frames text carry outputs]
723
+ match (top frames)
724
+ case [kind old] [marks (push [kind phase] (pop frames)) text carry outputs]
725
+
726
+ def proto-transduce-set-text [m text]
727
+ match m
728
+ case [marks frames old carry outputs] [marks frames text carry outputs]
729
+
730
+ ; The text being read, with \`more\` written at its end.
731
+ def proto-transduce-write [m more]
732
+ match m
733
+ case [marks frames [s mode] carry outputs] [marks frames [(string-join "" [s more]) mode] carry outputs]
734
+
735
+ ; The text ends: its scalar.
736
+ def proto-transduce-end-text [m]
737
+ match m
738
+ case [marks frames [s mode] carry outputs] [marks frames missing carry (push (scalar s) outputs)]
739
+ case _ m
740
+
741
+ ; The innermost frame's pending text and the containers its phase holds
742
+ ; open, closed: a range's bound, its object and the ranges' array, or a
743
+ ; reserved name list's array.
744
+ def proto-transduce-settle [m]
745
+ let [ended (match m (case [marks frames [s :token] carry outputs] (proto-transduce-end-text m)) (case _ m))]
746
+ match (proto-transduce-frame ended)
747
+ case [kind :from] (proto-transduce-phase (proto-transduce-emit ended [object-end array-end]) :done)
748
+ case [kind :to] (proto-transduce-phase (proto-transduce-emit ended [object-end array-end]) :done)
749
+ case [kind :next] (proto-transduce-phase (proto-transduce-emit ended [array-end]) :done)
750
+ case [kind :names] (proto-transduce-phase (proto-transduce-emit ended [array-end]) :done)
751
+ case _ ended
752
+
753
+ ; A statement begins: its frame, and the events that open its object.
754
+ def proto-transduce-begin [m kind]
755
+ match m
756
+ case [marks frames text carry outputs]
757
+ match kind
758
+ case :empty [marks (push [kind :start] frames) text carry outputs]
759
+ case :field-option [marks (push [kind :name] frames) ["" :token] carry (push-all [object-start (key "name")] outputs)]
760
+ case :entry [marks (push [kind :entry-name] frames) text carry (push object-start outputs)]
761
+ case :visible-message [marks (push [kind :visibility] frames) text carry (push-all [object-start (key "type") (scalar "message")] outputs)]
762
+ case :visible-enum [marks (push [kind :visibility] frames) text carry (push-all [object-start (key "type") (scalar "enum")] outputs)]
763
+ case :rpc [marks (push [kind :in] frames) text carry (push-all [object-start (key "type") (scalar "rpc")] outputs)]
764
+ case :map [marks (push [kind :key] frames) text carry (push-all [object-start (key "type") (scalar "map")] outputs)]
765
+ case _ [marks (push [kind :start] frames) text carry (push-all [object-start (key "type") (scalar (proto-transduce-type kind))] outputs)]
766
+
767
+ ; A statement ends: its pending text and containers close, then its
768
+ ; object.
769
+ def proto-transduce-finish-statement [m]
770
+ let [settled (proto-transduce-settle m)]
771
+ match settled
772
+ case [marks frames text carry outputs]
773
+ match (top frames)
774
+ case [:empty phase] [marks (pop frames) text carry outputs]
775
+ case _ [marks (pop frames) text carry (push object-end outputs)]
776
+
777
+ ; A token whose text begins the frame's text.
778
+ def proto-transduce-begin-text [m src]
779
+ proto-transduce-set-text m [src :token]
780
+
781
+ ; A reserved or an extensions statement's list: a range's bounds or a name.
782
+ def proto-transduce-list [m kind phase token]
783
+ let [src (get :src token)]
784
+ match phase
785
+ case :start (proto-transduce-phase m :list)
786
+ case :list
787
+ if (match [kind (get :name token) src] (case [:extensions n s] true) (case [k "#NR" s] true) (case [k n "-"] true) (case _ false))
788
+ proto-transduce-phase (proto-transduce-begin-text (proto-transduce-emit m [(key "ranges") array-start object-start (key "from")]) src) :from
789
+ proto-transduce-phase (proto-transduce-emit m [(key "names") array-start (scalar src)]) :names
790
+ case :next (proto-transduce-phase (proto-transduce-begin-text (proto-transduce-emit m [object-start (key "from")]) src) :from)
791
+ case :names
792
+ match src
793
+ case "," m
794
+ case ";" (proto-transduce-phase (proto-transduce-emit m [array-end]) :done)
795
+ case _ (proto-transduce-emit m [(scalar src)])
796
+ case _ m
797
+
798
+ ; A token the frame reads, no rule's text being open and the frame's own
799
+ ; text not ending at it.
800
+ def proto-transduce-frame-token [m token]
801
+ let [src (get :src token)]
802
+ match (proto-transduce-frame m)
803
+ case missing m
804
+ case [kind phase]
805
+ match [(proto-transduce-has-body kind) phase src]
806
+ case [true :body "}"] (proto-transduce-phase (proto-transduce-emit m [array-end]) :done)
807
+ case [true :body s] m
808
+ case [true p "{"] (proto-transduce-phase (proto-transduce-emit (proto-transduce-settle m) [(key "body") array-start]) :body)
809
+ case _
810
+ match kind
811
+ case :import
812
+ match [phase src]
813
+ case [:start s] (proto-transduce-phase m :keyword)
814
+ case [:keyword ";"] m
815
+ case [:keyword s] (proto-transduce-phase (proto-transduce-emit m [(key "modifier") (scalar s)]) :done)
816
+ case _ m
817
+ case :visible-message
818
+ match phase
819
+ case :visibility (proto-transduce-phase (proto-transduce-emit m [(key "visibility") (scalar src)]) :start)
820
+ case _ m
821
+ case :visible-enum
822
+ match phase
823
+ case :visibility (proto-transduce-phase (proto-transduce-emit m [(key "visibility") (scalar src)]) :start)
824
+ case _ m
825
+ case :oneof-field
826
+ match [phase src]
827
+ case [:start "."] (proto-transduce-phase m :dot)
828
+ case _ m
829
+ case :rpc
830
+ match [phase src]
831
+ case [:in "stream"] (proto-transduce-emit m [(key "inputStream") (scalar true)])
832
+ case [:out "stream"] (proto-transduce-emit m [(key "outputStream") (scalar true)])
833
+ case _ m
834
+ case :enum-value
835
+ match [phase src]
836
+ case [:start s] (proto-transduce-phase (proto-transduce-emit m [(key "name") (scalar s)]) :named)
837
+ case [:named "="] (proto-transduce-phase (proto-transduce-begin-text (proto-transduce-emit m [(key "number")]) "") :number)
838
+ case _ m
839
+ case :entry
840
+ match phase
841
+ case :entry-name (proto-transduce-phase (proto-transduce-emit m [(key "name") (scalar src)]) :colon)
842
+ case _ m
843
+ case :reserved (proto-transduce-list m kind phase token)
844
+ case :extensions (proto-transduce-list m kind phase token)
845
+ case _ m
846
+
847
+ ; Whether a token ends the frame's own text: an option name's \`=\`, an
848
+ ; enum value's \`;\`, a token other than a string literal after an
849
+ ; aggregate field's run of them, and a range bound's \`to\`, \`,\` or \`;\`.
850
+ def proto-transduce-ends [phase token]
851
+ let [src (get :src token)]
852
+ match phase
853
+ case :name (equal src "=")
854
+ case :number (equal src ";")
855
+ case :strings (if (equal (get :name token) "#ST") false true)
856
+ case :from (match src (case "to" true) (case "," true) (case ";" true) (case _ false))
857
+ case :to (match src (case "," true) (case ";" true) (case _ false))
703
858
  case _ false
704
859
 
705
- def proto-after [c p]
706
- let [parts (split c p)]
707
- string-join c (map (fn [i] (proto-at parts i)) (filter (fn [i] (proto-less 0 i)) (indices parts)))
708
-
709
- ; The letters protoc puts in upper case in an entry's name.
710
- 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"]]
711
-
712
- ; A part of a name, its first letter in upper case where it is one from a
713
- ; to z.
714
- def proto-capital [p]
715
- let [hits (filter (fn [l] (proto-begins (proto-at l 0) p)) proto-letters)]
716
- match (count hits)
717
- case 0 p
718
- case _ (string-join "" [(proto-at (proto-at hits 0) 1) (proto-after (proto-at (proto-at hits 0) 0) p)])
719
-
720
- ; The name protoc gives a map field's entry, which the reader gives the
721
- ; entry it makes: the field's name with each \`_\` dropped and the letter
722
- ; after it, and the first, in upper case, then \`Entry\` (\`map_field\` is
723
- ; \`MapFieldEntry\`).
724
- def proto-entry-name [name]
725
- string-join "" [(string-join "" (map proto-capital (split "_" name))) "Entry"]
726
-
727
- ; Whether every key of the object o is one of the keys allowed.
728
- def proto-only [allowed o]
729
- match (count (filter (fn [k] (proto-less (count (filter (fn [a] (proto-same-text a k)) allowed)) 1)) (keys o)))
730
- case 0 true
731
- case _ false
732
-
733
- def proto-empty [k o]
734
- match (kind (get k o))
735
- case :vector
736
- match (count (as-vector (get k o)))
737
- case 0 true
738
- case _ false
739
- case _ false
740
-
741
- ; The options the reader copies from a map field to its entry's key and
742
- ; value: \`features\`, and those whose name begins \`features.\`.
743
- def proto-feature-key [k]
744
- match k
745
- case "features" true
746
- case _ (proto-begins "features." k)
747
-
748
- ; Whether two options' values are the same: of one kind, and alike.
749
- def proto-same-value [a b]
750
- match [(kind a) (kind b)]
751
- case [:string :string] (proto-same-text a b)
752
- case [:number :number] (proto-same-number a b)
753
- case [:boolean :boolean]
754
- match [a b]
755
- case [true true] true
756
- case [false false] true
757
- case _ false
758
- case [:record :record] (proto-same-text (scalar-text csv-options a) (scalar-text csv-options b))
759
- case _ false
760
-
761
- ; Whether a field of an entry has the options the reader gives it: the
762
- ; features of the map field fo, by name, \`feats\`, and none where it has
763
- ; none.
764
- def proto-entry-options [fo feats g]
765
- match [(count feats) (proto-has "options" g)]
766
- case [0 false] true
767
- case [0 true] false
768
- case [_ false] false
769
- case _
770
- let [o (get "options" g)]
771
- match (kind o)
772
- case :record
773
- match (proto-same-number (count (keys o)) (count feats))
774
- case true (proto-same-number (count (filter (fn [k] (proto-same-value (get k o) (get k fo))) feats)) (count feats))
775
- case false false
776
- case _ false
777
-
778
- def proto-entry-field-keys ["name" "$name" "number" "label" "type" "typeName" "options"]
779
-
780
- ; Whether g is a field of an entry as the reader makes it, \`key\` 1 or
781
- ; \`value\` 2: optional, of a type or a type name, and with no option but
782
- ; the features the map field passes down.
783
- def proto-entry-field [name number fo feats g]
784
- match (kind g)
785
- case :record
786
- 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)]
787
- case [true "LABEL_OPTIONAL" :number true false true]
788
- match (proto-same-number (get "number" g) number)
789
- case true (proto-entry-options fo feats g)
790
- case false false
791
- case [true "LABEL_OPTIONAL" :number false true true]
792
- match (proto-same-number (get "number" g) number)
793
- case true (proto-entry-options fo feats g)
794
- case false false
795
- case _ false
796
- case _ false
797
-
798
- def proto-entry-keys ["name" "$name" "field" "nestedType" "enumType" "oneofDecl" "extension" "options"]
799
-
800
- ; Whether the nested type n is the entry the reader makes for the map
801
- ; field f, so that f written as a map reads back with n as it is: n named
802
- ; for f by protoc's rule, with no member but its fields, its empty lists
803
- ; and the option \`mapEntry\` alone, and its fields \`key\` and \`value\` as the
804
- ; reader makes them. A message the file declares, the option \`mapEntry\`
805
- ; in it, is any other one, and is written as a message.
806
- def proto-map-entry [f n]
807
- match [(kind (get "name" f)) (kind (get "name" n))]
808
- case [:string :string]
809
- match [(proto-same-text (proto-entry-name (get "name" f)) (get "name" n)) (proto-only proto-entry-keys n)]
810
- case [true true]
811
- let [o (proto-record "options" n)]
812
- match [(proto-empty "nestedType" n) (proto-empty "enumType" n) (proto-empty "oneofDecl" n) (proto-empty "extension" n) (count (keys o)) (get "mapEntry" o)]
813
- case [true true true true 1 true]
814
- let [kv (proto-list "field" n)]
815
- match (count kv)
816
- case 2
817
- let [fo (proto-record "options" f)]
818
- let [feats (filter proto-feature-key (keys fo))]
819
- match [(proto-entry-field "key" 1 fo feats (proto-at kv 0)) (proto-entry-field "value" 2 fo feats (proto-at kv 1))]
820
- case [true true] true
821
- case _ false
822
- case _ false
823
- case _ false
824
- case _ false
825
- case _ false
860
+ ; The frame's own text, ended by a token, and what the token says next.
861
+ def proto-transduce-text-end [m token]
862
+ let [src (get :src token)]
863
+ let [ended (proto-transduce-end-text m)]
864
+ match (proto-transduce-frame ended)
865
+ case [kind :name] (proto-transduce-phase ended :value)
866
+ case [kind :number] (proto-transduce-phase ended :done)
867
+ case [kind :strings] (proto-transduce-phase ended :done)
868
+ case [kind :from]
869
+ match src
870
+ case "to" (proto-transduce-phase (proto-transduce-begin-text (proto-transduce-emit ended [(key "to")]) "") :to)
871
+ case "," (proto-transduce-phase (proto-transduce-emit ended [object-end]) :next)
872
+ case _ (proto-transduce-phase (proto-transduce-emit ended [object-end array-end]) :done)
873
+ case [kind :to]
874
+ match src
875
+ case "," (proto-transduce-phase (proto-transduce-emit ended [object-end]) :next)
876
+ case _ (proto-transduce-phase (proto-transduce-emit ended [object-end array-end]) :done)
877
+ case _ ended
878
+
879
+ ; One token a pass consumed: written to a rule's text, or to the frame's
880
+ ; own text unless it ends it, or read by the frame.
881
+ def proto-transduce-token [m token]
882
+ match m
883
+ case [marks frames [s :rule] carry outputs] (proto-transduce-write m (get :src token))
884
+ case [marks frames [s :token] carry outputs]
885
+ match (proto-transduce-frame m)
886
+ case [kind phase]
887
+ if (proto-transduce-ends phase token)
888
+ match phase
889
+ case :strings (proto-transduce-frame-token (proto-transduce-text-end m token) token)
890
+ case _ (proto-transduce-text-end m token)
891
+ proto-transduce-write m (get :src token)
892
+ case _ (proto-transduce-frame-token m token)
893
+
894
+ ; The tokens a pass consumed, in order: at most three in this grammar,
895
+ ; and four read, so that a fifth fails rather than vanishes.
896
+ def proto-transduce-tokens [m rule]
897
+ let [tokens (get :tokens rule)]
898
+ match (count tokens)
899
+ case 0 m
900
+ case 1 (proto-transduce-token m (transduce-token 0 rule))
901
+ case 2 (proto-transduce-token (proto-transduce-token m (transduce-token 0 rule)) (transduce-token 1 rule))
902
+ case 3 (proto-transduce-token (proto-transduce-token (proto-transduce-token m (transduce-token 0 rule)) (transduce-token 1 rule)) (transduce-token 2 rule))
903
+ case 4 (proto-transduce-token (proto-transduce-token (proto-transduce-token (proto-transduce-token m (transduce-token 0 rule)) (transduce-token 1 rule)) (transduce-token 2 rule)) (transduce-token 3 rule))
904
+ case _ (fail :protocol-order "a rule pass consumed more tokens than any of the proto grammar's alternates")
905
+
906
+ ; The rule pushed by a pass's alternate, or "".
907
+ def proto-transduce-pushes [rule]
908
+ match (get :alt rule)
909
+ case missing ""
910
+ case alt (get :p alt)
911
+
912
+ ; Whether a pass's alternate replaces its rule.
913
+ def proto-transduce-replaced [rule]
914
+ match (get :alt rule)
915
+ case missing false
916
+ case alt
917
+ match (get :r alt)
918
+ case "" false
919
+ case _ true
826
920
 
827
- def proto-first [v]
828
- match (count v)
829
- case 0 -1
830
- case _ (proto-at v 0)
831
-
832
- def proto-last [v]
833
- match (count v)
834
- case 0 -1
835
- case _ (top v)
836
-
837
- def proto-entry-named [f n]
838
- match (proto-named (get "typeName" f) n)
839
- case true (proto-map-entry f n)
840
- case false false
841
-
842
- ; The nested type a field is written with, its index, or -1: a group's
843
- ; message, and a map field's entry. A map field is a repeated field whose
844
- ; type is named for a nested type that is the entry the reader would make
845
- ; for it (\`proto-map-entry\`).
846
- def proto-attached [ns f]
847
- match (get "type" f)
848
- case "TYPE_GROUP" (proto-first (filter (fn [t] (proto-named (get "typeName" f) (proto-at ns t))) (indices ns)))
921
+ ; A member's rule opens in a frame: its key, and its text or its
922
+ ; container begun; the marker its close will act on.
923
+ def proto-transduce-member-open [m kind phase member rule]
924
+ match member
925
+ case :options [(proto-transduce-emit (proto-transduce-settle m) [(key "options") array-start]) :array]
926
+ case :strings [(proto-transduce-phase (proto-transduce-set-text (proto-transduce-emit m [(key "value")]) ["" :token]) :strings) :none]
927
+ case :type
928
+ match [kind phase]
929
+ case [:map :key] [(proto-transduce-set-text (proto-transduce-phase (proto-transduce-emit m [(key "keyType")]) :value) ["" :rule]) :text]
930
+ case [:map p] [(proto-transduce-set-text (proto-transduce-emit m [(key "valueType")]) ["" :rule]) :text]
931
+ case [:rpc :in] [(proto-transduce-set-text (proto-transduce-phase (proto-transduce-emit m [(key "inputType")]) :out) ["" :rule]) :text]
932
+ case [:rpc p] [(proto-transduce-set-text (proto-transduce-emit m [(key "outputType")]) ["" :rule]) :text]
933
+ case "value"
934
+ match (proto-transduce-pushes rule)
935
+ case "constant$alt5" [(proto-transduce-emit m [(key "value") array-start]) :array]
936
+ case _ [(proto-transduce-set-text (proto-transduce-emit m [(key "value")]) ["" :rule]) :text]
937
+ case "fieldType"
938
+ match [kind phase]
939
+ case [:oneof-field :dot] [(proto-transduce-set-text (proto-transduce-phase (proto-transduce-emit m [(key "fieldType")]) :typed) ["." :rule]) :text]
940
+ case _ [(proto-transduce-set-text (proto-transduce-emit m [(key "fieldType")]) ["" :rule]) :text]
941
+ case name [(proto-transduce-set-text (proto-transduce-emit m [(key name)]) ["" :rule]) :text]
942
+
943
+ ; A rule opens that begins no statement: a member of the innermost frame,
944
+ ; or part of the text being read, or nothing.
945
+ def proto-transduce-inner-open [m rule]
946
+ match m
947
+ case [marks frames [s :rule] carry outputs] [m :none]
849
948
  case _
850
- match (get "label" f)
851
- case "LABEL_REPEATED"
852
- match [(proto-has "type" f) (proto-has "typeName" f) (proto-has "oneofIndex" f) (proto-has "extendee" f)]
853
- case [false true false false] (proto-first (filter (fn [t] (proto-entry-named f (proto-at ns t))) (indices ns)))
854
- case _ -1
855
- case _ -1
856
-
857
- ; Whether a field is in a declared oneof: a proto3 \`optional\` field's
858
- ; oneof is synthetic, and the field is written \`optional\`.
859
- def proto-in-oneof [f]
860
- match (proto-has "oneofIndex" f)
861
- case true
862
- match (get "proto3Optional" f)
863
- case true false
864
- case _ true
865
- case false false
866
-
867
- def proto-same-oneof [fs j k]
868
- let [a (proto-at fs j)]
869
- let [b (proto-at fs k)]
870
- match (proto-in-oneof b)
871
- case true (proto-same-number (get "oneofIndex" a) (get "oneofIndex" b))
872
- case false false
873
-
874
- ; A unit is a field, or a declared oneof's fields, which are adjacent in
875
- ; the tree; it is written at its first field.
876
- def proto-unit-start [fs j]
877
- match (proto-in-oneof (proto-at fs j))
878
- case false true
879
- case true
880
- match (count (filter (fn [k] (proto-less k j)) (filter (fn [k] (proto-same-oneof fs j k)) (indices fs))))
881
- case 0 true
882
- case _ false
883
-
884
- def proto-unit-fields [fs j]
885
- match (proto-in-oneof (proto-at fs j))
886
- case false [j]
887
- case true (filter (fn [k] (proto-same-oneof fs j k)) (indices fs))
888
-
889
- def proto-attached-of [att ks]
890
- filter (fn [t] (proto-less -1 t)) (map (fn [k] (proto-at att k)) ks)
891
-
892
- ; The nested types written as messages, between two attached ones.
893
- def proto-plain-nested [ns att lo hi]
894
- filter
895
- fn [t]
896
- match (proto-member t att)
897
- case true false
898
- case false
899
- match [(proto-less lo t) (proto-less t hi)]
900
- case [true true] true
901
- case _ false
902
- indices ns
903
-
904
- ; The messages due before a unit: those after the nested type of the last
905
- ; attached field before it and before the unit's own first.
906
- def proto-nested-before [indent ns bodies fs att j]
907
- let [mine (proto-attached-of att (proto-unit-fields fs j))]
908
- match (count mine)
909
- case 0 ""
949
+ match (proto-transduce-frame m)
950
+ case missing [m :none]
951
+ case [kind phase]
952
+ match (proto-transduce-member kind (get :name rule))
953
+ case missing [m :none]
954
+ case member
955
+ match m
956
+ case [marks frames [s :token] carry outputs]
957
+ match [member phase]
958
+ case [:strings :strings] [m :none]
959
+ case _ (proto-transduce-member-open (proto-transduce-settle m) kind phase member rule)
960
+ case _ (proto-transduce-member-open m kind phase member rule)
961
+
962
+ ; An open pass, no marker handed on: the file, a statement, or a rule
963
+ ; inside one.
964
+ def proto-transduce-fresh [m rule]
965
+ let [name (get :name rule)]
966
+ match name
967
+ case "__start__" [(proto-transduce-emit (match m (case [marks frames text carry outputs] [marks (push [:file :body] frames) text carry outputs])) [object-start (key "type") (scalar "file") (key "body") array-start]) :file]
910
968
  case _
911
- let [lo (proto-last (proto-attached-of att (filter (fn [k] (proto-less k j)) (indices fs))))]
912
- string-join "\\n"
913
- map (fn [t] (proto-message indent (proto-at ns t) (proto-at bodies t))) (proto-plain-nested ns att lo (proto-at mine 0))
914
-
915
- def proto-nested-after [indent ns bodies att]
916
- let [lo (proto-last (filter (fn [t] (proto-less -1 t)) att))]
917
- string-join "\\n"
918
- map (fn [t] (proto-message indent (proto-at ns t) (proto-at bodies t))) (proto-plain-nested ns att lo 4294967296)
919
-
920
- ; The declared oneofs: all but the synthetic ones of proto3 \`optional\`.
921
- def proto-declared-oneofs [m]
922
- 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)))]
923
- filter (fn [i] (match (proto-member i synthetic) (case true false) (case false true))) (indices (proto-list "oneofDecl" m))
924
-
925
- ; The declared oneofs no field is in.
926
- def proto-empty-oneofs [m]
927
- let [used (map (fn [f] (get "oneofIndex" f)) (filter proto-in-oneof (proto-list "field" m)))]
928
- filter (fn [i] (match (proto-member i used) (case true false) (case false true))) (proto-declared-oneofs m)
929
-
930
- def proto-oneof-block [indent m i members]
931
- let [o (proto-at (proto-list "oneofDecl" m) i)]
932
- proto-block indent (string-join "" ["oneof " (proto-name "a oneof" o)])
933
- proto-lines
934
- vector
935
- proto-option-lines (proto-deeper indent) (proto-record "options" o)
936
- members
937
-
938
- ; The empty oneofs due before a unit's oneof: those after the last oneof
939
- ; written before it.
940
- def proto-oneofs-before [indent m fs j]
941
- let [f (proto-at fs j)]
942
- match (proto-in-oneof f)
943
- case false ""
944
- case true
945
- 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))))]
946
- string-join "\\n"
947
- map
948
- fn [i]
949
- proto-oneof-block indent m i ""
950
- filter (fn [i] (match [(proto-less lo i) (proto-less i (get "oneofIndex" f))] (case [true true] true) (case _ false))) (proto-empty-oneofs m)
951
-
952
- def proto-oneofs-after [indent m]
953
- let [lo (proto-last (map (fn [f] (get "oneofIndex" f)) (filter proto-in-oneof (proto-list "field" m))))]
954
- string-join "\\n"
955
- map
956
- fn [i]
957
- proto-oneof-block indent m i ""
958
- filter (fn [i] (proto-less lo i)) (proto-empty-oneofs m)
959
-
960
- def proto-group-block [syntax indent f body labelled]
961
- proto-block indent
962
- string-join ""
963
- vector
964
- match labelled
965
- case true (proto-label syntax f)
966
- case false ""
967
- "group "
968
- proto-text "a group" "typeName" f
969
- " = "
970
- proto-num (get "number" f)
971
- body
972
-
973
- def proto-map-line [indent f n]
974
- let [kv (proto-list "field" n)]
975
- string-join ""
976
- vector
977
- indent
978
- "map<"
979
- proto-type (proto-at kv 0)
980
- ", "
981
- proto-type (proto-at kv 1)
982
- "> "
983
- proto-name "a map field" f
984
- " = "
985
- proto-num (get "number" f)
986
- proto-field-options f
987
- ";"
988
-
989
- ; A field's statement: a field, a map field, or a group with its body. A
990
- ; group outside a oneof is written with the body the message's own were
991
- ; written with; one in a oneof, a level deeper, has its body written there.
992
- def proto-field-statement [self syntax indent ns bodies att fs k labelled]
993
- let [f (proto-at fs k)]
994
- let [t (proto-at att k)]
995
- match (proto-less t 0)
996
- case true (proto-field-line syntax indent f labelled)
997
- case false
998
- match (get "type" f)
999
- case "TYPE_GROUP"
1000
- match labelled
1001
- case true (proto-group-block syntax indent f (proto-at bodies t) labelled)
1002
- case false (proto-group-block syntax indent f (self self syntax (proto-deeper indent) (proto-at ns t)) labelled)
1003
- case _ (proto-map-line indent f (proto-at ns t))
1004
-
1005
- def proto-unit-text [self syntax indent m ns bodies fs att j]
1006
- let [f (proto-at fs j)]
1007
- match (proto-in-oneof f)
1008
- case false (proto-field-statement self syntax indent ns bodies att fs j true)
1009
- case true
1010
- proto-oneof-block indent m (get "oneofIndex" f)
1011
- string-join "\\n"
1012
- map (fn [k] (proto-field-statement self syntax (proto-deeper indent) ns bodies att fs k false)) (proto-unit-fields fs j)
1013
-
1014
- def proto-unit [self syntax indent m ns bodies fs att j]
1015
- match (proto-unit-start fs j)
1016
- case false ""
1017
- case true
1018
- proto-lines
1019
- vector
1020
- proto-nested-before indent ns bodies fs att j
1021
- proto-oneofs-before indent m fs j
1022
- proto-unit-text self syntax indent m ns bodies fs att j
1023
-
1024
- ; A message's extension ranges, reserved ranges and reserved names, in the
1025
- ; order the tree holds them.
1026
- def proto-range-key [syntax indent maxv m k]
1027
- match k
1028
- case "extensionRange"
1029
- string-join "\\n"
1030
- map
1031
- fn [r]
1032
- string-join "" [indent "extensions " (proto-range-ex maxv r) (proto-plain-options r) ";"]
1033
- proto-list "extensionRange" m
1034
- case "reservedRange" (proto-reserved-ranges indent (string-join ", " (map (fn [r] (proto-range-ex maxv r)) (proto-list "reservedRange" m))))
1035
- case "reservedName" (proto-reserved-names syntax indent (proto-list "reservedName" m))
1036
- case _ ""
1037
-
1038
- ; A message's body, the one definition the render recurses through, as
1039
- ; \`self\`. Every nested type's body is written first, a level deeper, and
1040
- ; the rest of the body places them: the recursion then nests a few
1041
- ; evaluation levels a message level, well within alchemy's bound for the
1042
- ; 100 levels the reader accepts, where placing a message among the fields
1043
- ; first would nest some twenty.
1044
- def proto-message-body [self syntax indent m]
1045
- let [ns (proto-list "nestedType" m)]
1046
- let [bodies (map (fn [n] (self self syntax (proto-deeper indent) n)) ns)]
1047
- proto-message-parts self syntax indent m ns bodies
1048
-
1049
- def proto-message-parts [self syntax indent m ns bodies]
1050
- let [fs (proto-list "field" m)]
1051
- let [att (map (fn [f] (proto-attached ns f)) fs)]
1052
- proto-lines
1053
- vector
1054
- proto-option-lines indent (proto-record "options" m)
1055
- proto-lines (map (fn [j] (proto-unit self syntax indent m ns bodies fs att j)) (indices fs))
1056
- proto-nested-after indent ns bodies att
1057
- proto-oneofs-after indent m
1058
- string-join "\\n" (map (fn [e] (proto-enum syntax indent e)) (proto-list "enumType" m))
1059
- string-join "\\n" (map (fn [f] (proto-extend syntax indent f)) (proto-list "extension" m))
1060
- proto-lines (map (fn [k] (proto-range-key syntax indent (proto-max m) m k)) (keys m))
1061
-
1062
- ; A message's block around its body.
1063
- def proto-message [indent m body]
1064
- proto-block indent (string-join "" [(proto-visibility m) "message " (proto-name "a message" m)]) body
1065
-
1066
- ; ---- enums, services, extensions
1067
-
1068
- def proto-enum-key [syntax indent e k]
1069
- match k
1070
- case "value"
1071
- string-join "\\n"
1072
- map
1073
- fn [v]
1074
- string-join "" [indent (proto-name "an enum value" v) " = " (proto-num (get "number" v)) (proto-plain-options v) ";"]
1075
- proto-list "value" e
1076
- case "options" (proto-option-lines indent (proto-record "options" e))
1077
- case "reservedRange" (proto-reserved-ranges indent (string-join ", " (map proto-range-in (proto-list "reservedRange" e))))
1078
- case "reservedName" (proto-reserved-names syntax indent (proto-list "reservedName" e))
1079
- case _ ""
1080
-
1081
- def proto-enum [syntax indent e]
1082
- proto-block indent (string-join "" [(proto-visibility e) "enum " (proto-name "an enum" e)])
1083
- proto-lines (map (fn [k] (proto-enum-key syntax (proto-deeper indent) e k)) (keys e))
1084
-
1085
- def proto-stream [flag]
1086
- match flag
1087
- case true "stream "
1088
- case _ ""
1089
-
1090
- def proto-method [indent r]
1091
- 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) ")"])]
1092
- match (proto-has "options" r)
1093
- case false (string-join "" [indent head ";"])
1094
- case true (proto-block indent head (proto-option-lines (proto-deeper indent) (proto-record "options" r)))
1095
-
1096
- def proto-service [indent s]
1097
- proto-block indent (string-join "" ["service " (proto-name "a service" s)])
1098
- proto-lines
1099
- vector
1100
- proto-option-lines (proto-deeper indent) (proto-record "options" s)
1101
- string-join "\\n" (map (fn [r] (proto-method (proto-deeper indent) r)) (proto-list "method" s))
1102
-
1103
- ; An extension, in an \`extend\` block of its own.
1104
- def proto-extend [syntax indent f]
1105
- proto-block indent (string-join "" ["extend " (proto-text "an extension" "extendee" f)])
1106
- proto-field-line syntax (proto-deeper indent) f true
1107
-
1108
- ; ---- the file
1109
-
1110
- def proto-syntax-line [file]
1111
- match (get "syntax" file)
1112
- case "proto2" "syntax = \\"proto2\\";"
1113
- case "proto3" "syntax = \\"proto3\\";"
1114
- case "editions"
1115
- match (get "edition" file)
1116
- case "EDITION_2023" "edition = \\"2023\\";"
1117
- case "EDITION_2024" "edition = \\"2024\\";"
1118
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its edition is not one a .proto file declares")
1119
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its syntax is not proto2, proto3 or editions")
1120
-
1121
- def proto-import-kind [file i]
1122
- match (proto-member i (proto-list "publicDependency" file))
1123
- case true "public "
1124
- case false
1125
- match (proto-member i (proto-list "weakDependency" file))
1126
- case true "weak "
1127
- case false ""
1128
-
1129
- def proto-dependency [file i d]
1130
- match (kind d)
1131
- case :string (string-join "" ["import " (proto-import-kind file i) (proto-literal d) ";"])
1132
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: an import is not a string")
1133
-
1134
- ; The package, the imports and the file's options, in the order the tree
1135
- ; holds them.
1136
- def proto-head-key [file k]
1137
- match k
1138
- case "package" (string-join "" ["package " (proto-text "the file" "package" file) ";"])
1139
- case "dependency"
1140
- let [ds (proto-list "dependency" file)]
1141
- string-join "\\n" (map (fn [i] (proto-dependency file i (proto-at ds i))) (indices ds))
1142
- case "optionDependency"
1143
- string-join "\\n"
1144
- map
1145
- fn [d]
1146
- string-join "" ["import option " (proto-literal d) ";"]
1147
- proto-list "optionDependency" file
1148
- case "options" (proto-option-lines "" (proto-record "options" file))
1149
- case _ ""
1150
-
1151
- def proto-file [file]
1152
- match (kind file)
1153
- case :record
1154
- let [syntax (get "syntax" file)]
1155
- string-join ""
1156
- vector
1157
- proto-lines
1158
- vector
1159
- proto-syntax-line file
1160
- proto-lines (map (fn [k] (proto-head-key file k)) (keys file))
1161
- string-join "\\n" (map (fn [m] (proto-message "" m (proto-message-body proto-message-body syntax " " m))) (proto-list "messageType" file))
1162
- string-join "\\n" (map (fn [e] (proto-enum syntax "" e)) (proto-list "enumType" file))
1163
- string-join "\\n" (map (fn [s] (proto-service "" s)) (proto-list "service" file))
1164
- string-join "\\n" (map (fn [f] (proto-extend syntax "" f)) (proto-list "extension" file))
1165
- "\\n"
1166
- case _ (fail :unrepresentable "the tree is not a FileDescriptorProto: its root is not an object")
1167
-
1168
- ; The render: the tree's events in, the file's text out.
1169
- def proto-render [input]
1170
- concat-map proto-file
1171
- select root
1172
- as-events (scan-emit :other proto-mark-step proto-mark-finish (events input))
969
+ match (proto-transduce-statement name)
970
+ case missing (proto-transduce-inner-open m rule)
971
+ case kind [(proto-transduce-begin m kind) :statement]
972
+
973
+ ; A marker's close.
974
+ def proto-transduce-act [m mark]
975
+ match mark
976
+ case :text (proto-transduce-end-text m)
977
+ case :array (proto-transduce-emit m [array-end])
978
+ case :statement (proto-transduce-finish-statement m)
979
+ case :file
980
+ match m
981
+ case [marks frames text carry outputs] [marks (pop frames) text carry (push-all [array-end object-end] outputs)]
982
+ case _ m
983
+
984
+ def proto-transduce-open [m rule]
985
+ match m
986
+ case [marks frames text carry outputs]
987
+ match carry
988
+ case missing
989
+ match (proto-transduce-fresh m rule)
990
+ case [opened mark]
991
+ let [read (proto-transduce-tokens opened rule)]
992
+ match read
993
+ case [ms fs t c os]
994
+ match (proto-transduce-replaced rule)
995
+ case true [ms fs t mark os]
996
+ case _ [(push mark ms) fs t missing os]
997
+ case mark
998
+ let [read (proto-transduce-tokens [marks frames text missing outputs] rule)]
999
+ match read
1000
+ case [ms fs t c os]
1001
+ match (proto-transduce-replaced rule)
1002
+ case true [ms fs t mark os]
1003
+ case _ [(push mark ms) fs t missing os]
1004
+
1005
+ def proto-transduce-close [m rule]
1006
+ match m
1007
+ case [marks frames text carry outputs]
1008
+ let [mark (top marks)]
1009
+ let [read (proto-transduce-tokens [(pop marks) frames text carry outputs] rule)]
1010
+ match (proto-transduce-replaced rule)
1011
+ case true
1012
+ match read
1013
+ case [ms fs t c os] [ms fs t mark os]
1014
+ case _ (proto-transduce-act read mark)
1015
+
1016
+ def proto-transduce-step [state pass]
1017
+ match state
1018
+ case [started marks frames text carry]
1019
+ let [m [marks frames text carry []]]
1020
+ let [next (match pass (case (open rule) (proto-transduce-open m rule)) (case (close rule) (proto-transduce-close m rule)))]
1021
+ match next
1022
+ case [ms fs t c outputs] (transition [true ms fs t c] outputs)
1023
+
1024
+ ; A source that made no pass is the empty file.
1025
+ def proto-transduce-finish [state]
1026
+ match state
1027
+ case [false marks frames text carry] [object-start (key "type") (scalar "file") (key "body") array-start array-end object-end]
1028
+ case [true [] [] text carry] []
1029
+ case _ (fail :protocol-order "the rule passes ended inside a statement, which a file's never do")
1030
+
1031
+ def proto-transduce [input]
1032
+ as-events (scan-emit [false [] [] missing missing] proto-transduce-step proto-transduce-finish (parser-events input))
1173
1033
  ` })
1174
1034
  })
1175
1035