@tabnas/proto 0.6.10 → 0.6.12

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