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