@tabnas/expr 0.5.15 → 0.5.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,479 @@
1
+ "use strict";
2
+ /* Copyright (c) 2021-2026 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.translate = translate;
5
+ const TRANSLATION = Object.freeze({
6
+ manifest: `{
7
+ "$schema": "https://tabnas.dev/schema/plugin.schema.json",
8
+ "name": "@tabnas/expr",
9
+ "go": "github.com/tabnas/expr/go",
10
+ "rust": "tabnas-expr",
11
+ "description": "Expression syntax with operators and precedence for the tabnas engine.",
12
+ "base": "@tabnas/jsonic",
13
+ "engine": "@tabnas/parser",
14
+ "languageId": "expr",
15
+ "pluginKind": "grammar",
16
+ "specDir": "test/spec",
17
+ "clib": {
18
+ "dir": "go/clib",
19
+ "library": "libtabnasexpr",
20
+ "abi": "v1",
21
+ "errorCodes": [
22
+ "usage",
23
+ "grammar",
24
+ "handle",
25
+ "internal"
26
+ ]
27
+ },
28
+ "errorCodes": [],
29
+ "docs": "https://tabnas.dev/docs/packages",
30
+ "repository": "https://github.com/tabnas/expr",
31
+ "versionSource": "ts/package.json",
32
+ "translate": {
33
+ "reads": "tree",
34
+ "writes": "tree",
35
+ "root": "any",
36
+ "schema": "expr",
37
+ "embed": "alchemy/embed.alc",
38
+ "render": "alchemy/render.alc",
39
+ "loss": [
40
+ "Comments, spacing, implicit lists and maps, and the document's own spelling of strings and numbers are not kept: every list is written in brackets, every object in braces with its keys quoted, every string double-quoted and every number as JSON writes it.",
41
+ "An operation of the default operators is written in infix, and a term that would need parentheses, which read back as a group, is written as a list instead, which reads back as the same tree.",
42
+ "A list whose first element is an object whose src member is a default operator's source text, as the reader's own value describes an operator, is written with that text in the object's place, as an operation where its count makes one: only the src member is read, and the object's other members, the token the operator was read from among them, are not kept.",
43
+ "A negative number is written with its sign, which reads back as the operator - applied to the number's magnitude.",
44
+ "A number that is not finite is written as the string Infinity, -Infinity or NaN."
45
+ ]
46
+ }
47
+ }
48
+ `,
49
+ embed: Object.freeze({ entry: "expr-embed", source: `; Expressions' embedding: a plain tree as an expression document's tree,
50
+ ; and back again. This is a library of definitions, each named
51
+ ; \`expr-...\`, which a host links with its own program and with the render
52
+ ; (alchemy's \`compile_sources\`). It has two entry points: \`expr-embed\`
53
+ ; takes a plain tree's events to an expression tree's, and \`expr-unembed\`,
54
+ ; its reverse, takes them back. Nothing else here is the host's to call.
55
+ ;
56
+ ; Both are the identity. The reader builds its trees on jsonic's values,
57
+ ; so every plain tree is already an expression tree: a list whose first
58
+ ; element is a default operator and whose count makes an operation is
59
+ ; one, and the render writes it in infix, the operator being its source
60
+ ; text (\`+\`, \`-\`, \`*\`, \`/\`, \`%\`, or \`(\` for a group) or an object that
61
+ ; describes it, whose \`src\` member is that text, as the reader's own
62
+ ; value carries it; every other value is a value of jsonic's, which the
63
+ ; render writes as jsonic. A list that only looks like an operation
64
+ ; reads back as the same tree either way, once each operator is reduced
65
+ ; to its source text as the shared fixtures' simplifier, Go's \`Simplify\`
66
+ ; and Rust's \`simplify\` reduce it, so nothing needs refusing. What the
67
+ ; round trip changes is what the render's conventions change (the
68
+ ; manifest's loss list): an operator's description is written as its
69
+ ; source text, a negative number reads back as the operator \`-\` applied
70
+ ; to its magnitude, and a number that is not finite as a string.
71
+
72
+ ; The embedding: a plain tree's events in, the same events out.
73
+ def expr-embed [input]
74
+ input
75
+
76
+ ; The reverse: an expression tree's events in, the same events out.
77
+ def expr-unembed [input]
78
+ input
79
+ ` }),
80
+ render: Object.freeze({ entry: "expr-render", source: `; Expressions' render: a tree's events written as an expression document,
81
+ ; jsonic text with the plugin's default operators. This is a library of
82
+ ; definitions, each named \`expr-...\`, which a host links with its own
83
+ ; program (alchemy's \`compile_sources\`); its entry point is \`expr-render\`,
84
+ ; and nothing else here is the host's to call.
85
+ ;
86
+ ; The tree is the one the reader builds with its default options:
87
+ ; jsonic's values, in which an operation is a list whose first element is
88
+ ; its operator and whose other elements are its terms. The reader's own
89
+ ; value puts the operator's description there, an object whose \`src\`
90
+ ; member is the operator's source text (TypeScript's parse, and Rust's
91
+ ; once realized, give it so); the shared fixtures' simplifier, Go's
92
+ ; \`Simplify\` and Rust's \`simplify\` reduce each description to that text,
93
+ ; \`+\`, \`-\`, \`*\`, \`/\` or \`%\` (\`1+2*3\` is \`["+", 1, ["*", 2, 3]]\`, \`-1\` is
94
+ ; \`["-", 1]\`) or \`(\` for a group (\`(1)\` is \`["(", 1]\`, \`()\` is \`["("]\`).
95
+ ; The render reads both: the first element of a list is the operator its
96
+ ; text is, or the operator an object describes, read by its \`src\` member
97
+ ; as those simplifiers read it, when that is a default operator's source.
98
+ ;
99
+ ; Every value is written so that the reader builds the same tree from it:
100
+ ;
101
+ ; - An operation is written in infix: \`a+b\` for a list of an infix
102
+ ; operator and two terms, \`-a\` or \`+a\` for a list of \`-\` or \`+\` and one
103
+ ; term, \`(a)\` for a group and \`()\` for an empty one. The default
104
+ ; operators' binding powers decide where a term needs grouping: \`*\`,
105
+ ; \`/\` and \`%\` bind tighter than \`+\` and \`-\`, every infix operator binds
106
+ ; to the left, and a prefix operator binds tighter than any infix one.
107
+ ; Parentheses would read back as a group, a list \`["(", ...]\`, so a
108
+ ; term that needs grouping (\`["+", 1, 2]\` under \`*\`, or on the right of
109
+ ; \`-\`) is written as a list instead, \`["+",1,2]*3\`, which the reader
110
+ ; builds as the same list.
111
+ ; - Any other list, one whose first element is no operator or which has
112
+ ; another number of terms (\`["*", 1]\`, \`["+", 1, 2, 3]\`), is written as
113
+ ; a list, \`[a,b]\`, each element in infix where it is an operation.
114
+ ; - An object is written \`{"k":v}\`, every key quoted.
115
+ ; - A string is quoted (JSON's escapes, which jsonic reads), so a word is
116
+ ; written \`"a"\`; a number by its lexeme, or as JSON writes it; a
117
+ ; boolean and null by their names. A negative number is written with
118
+ ; its sign, which the reader reads as the operator \`-\` applied to the
119
+ ; number's magnitude, and a number that is not finite, which jsonic
120
+ ; has no spelling for, as the string \`Infinity\`, \`-Infinity\` or \`NaN\`.
121
+ ;
122
+ ; No white space is written, since jsonic reads a space between two
123
+ ; values as an implicit list, and the document ends with one line break.
124
+ ;
125
+ ; Whether a list is written as an operation is known from its first
126
+ ; element and its count, so a list whose first element is a default
127
+ ; operator is held until it ends, its elements' texts with it, and then
128
+ ; written. A list whose first element is an object is held until that
129
+ ; object ends, since the object may describe an operator: if it does, the
130
+ ; list is held to its end as an operation is, and if not, the list is
131
+ ; written as it is read from then on. Every other list, and every object,
132
+ ; is written as it is read, one element at a time, so a document's own
133
+ ; lists and objects stream and only its operations, and the first object
134
+ ; of a list, are held. Events no tree has fail with PROTOCOL_ORDER_ERROR.
135
+ ;
136
+ ; The state is a stack of markers, one for each container the render is
137
+ ; inside:
138
+ ;
139
+ ; [:pending mode] a list whose first element is due, which
140
+ ; decides how it is written; mode is
141
+ ; :stream where it is written as it is
142
+ ; read and :hold inside a held list
143
+ ; [:list :first] a list whose first element, an object,
144
+ ; is being held, which decides how the
145
+ ; list is written
146
+ ; [:list :stream first] a list written as it is read; first
147
+ ; until an element is written
148
+ ; [:list :hold items] a held list, and the items read so far
149
+ ; [:map :stream first :key] an object written as it is read, its
150
+ ; next key or its end due
151
+ ; [:map :stream :value] the same, a member's value due
152
+ ; [:map :hold parts :key] an object inside a held list, or the
153
+ ; first element of a list, and its
154
+ ; members' texts
155
+ ; [:map :hold parts [:value k]] the same, the value of the key k due
156
+ ;
157
+ ; An item is \`[text class list head]\`: the text a value is written as;
158
+ ; its class, \`:infix2\` for an operation of \`+\` or \`-\`, \`:infix3\` for one
159
+ ; of \`*\`, \`/\` or \`%\`, and \`:atom\` for any other value, which binds as
160
+ ; tightly as a term can; the text of the value as a list, for a term that
161
+ ; needs grouping; and the operator a string is or an object describes, or
162
+ ; \`:none\`. A list's first item that is an operator is held as the
163
+ ; operator's text, so a description is written as its source, as the
164
+ ; simplifiers reduce it, wherever its list is written. Once the root
165
+ ; value is written the state is \`[:done]\`, and nothing may follow it.
166
+
167
+ ; The operator a value is, when it is the source of a default one.
168
+ def expr-op [value]
169
+ match (kind value)
170
+ case :string
171
+ match value
172
+ case "+" value
173
+ case "-" value
174
+ case "*" value
175
+ case "/" value
176
+ case "%" value
177
+ case "(" value
178
+ case _ :none
179
+ case _ :none
180
+
181
+ ; The operator a held object's member describes: the default operator a
182
+ ; \`src\` member's text names, or :none for any other member.
183
+ def expr-src-op [part]
184
+ match part
185
+ case "\\"src\\":\\"+\\"" "+"
186
+ case "\\"src\\":\\"-\\"" "-"
187
+ case "\\"src\\":\\"*\\"" "*"
188
+ case "\\"src\\":\\"/\\"" "/"
189
+ case "\\"src\\":\\"%\\"" "%"
190
+ case "\\"src\\":\\"(\\"" "("
191
+ case _ :none
192
+
193
+ def expr-is-op [op]
194
+ match op
195
+ case :none false
196
+ case _ true
197
+
198
+ ; The operator an object describes, from its members' texts: the one its
199
+ ; \`src\` member names, as the reader's description of an operator does, or
200
+ ; :none.
201
+ def expr-described [parts]
202
+ let [ops (filter expr-is-op (map expr-src-op parts))]
203
+ match (count ops)
204
+ case 0 :none
205
+ case _ (top ops)
206
+
207
+ ; An item as its list holds it: the first one, where it is an operator,
208
+ ; as that operator's text, so that a description is written as the
209
+ ; source it describes; any other as it is.
210
+ def expr-lead [items item]
211
+ match (count items)
212
+ case 0
213
+ match item
214
+ case [_ _ _ :none] item
215
+ case [_ _ _ op] (expr-scalar-item op)
216
+ case _ item
217
+
218
+ ; An infix operator's class.
219
+ def expr-level [op]
220
+ match op
221
+ case "+" :infix2
222
+ case "-" :infix2
223
+ case _ :infix3
224
+
225
+ ; A number: its lexeme, or as JSON writes it; one that is not finite as
226
+ ; a string.
227
+ def expr-number [value]
228
+ match (number-class value)
229
+ case :finite (scalar-text csv-options value)
230
+ case :infinity (quoted "Infinity")
231
+ case :negative-infinity (quoted "-Infinity")
232
+ case :nan (quoted "NaN")
233
+
234
+ def expr-scalar-text [value]
235
+ match (kind value)
236
+ case :string (quoted value)
237
+ case :number (expr-number value)
238
+ case :boolean (scalar-text csv-options value)
239
+ case :null "null"
240
+
241
+ def expr-scalar-item [value]
242
+ let [text (expr-scalar-text value)]
243
+ [text :atom text (expr-op value)]
244
+
245
+ def expr-text [item]
246
+ match item
247
+ case [text _ _ _] text
248
+
249
+ ; A held list's elements written as a list.
250
+ def expr-list [items]
251
+ string-join "" ["[" (string-join "," (map expr-text items)) "]"]
252
+
253
+ ; A left term of an infix operator of the class given: an operation that
254
+ ; binds as tightly or more, or the term as a list.
255
+ def expr-left [item level]
256
+ match item
257
+ case [text :atom _ _] text
258
+ case [text :infix3 _ _] text
259
+ case [text :infix2 list _]
260
+ match level
261
+ case :infix2 text
262
+ case _ list
263
+
264
+ ; A right term: an operation that binds more tightly, or the term as a
265
+ ; list.
266
+ def expr-right [item level]
267
+ match item
268
+ case [text :atom _ _] text
269
+ case [text :infix3 list _]
270
+ match level
271
+ case :infix2 text
272
+ case _ list
273
+ case [_ :infix2 list _] list
274
+
275
+ ; A prefix operator's term: one that binds as tightly, or the term as a
276
+ ; list.
277
+ def expr-term [item]
278
+ match item
279
+ case [text :atom _ _] text
280
+ case [_ _ list _] list
281
+
282
+ ; An infix operation, when the first element is an infix operator.
283
+ def expr-infix [op a b list]
284
+ match op
285
+ case :none [list :atom list :none]
286
+ case "(" [list :atom list :none]
287
+ case _
288
+ let [level (expr-level op)]
289
+ [(string-join "" [(expr-left a level) op (expr-right b level)]) level list :none]
290
+
291
+ ; A held list, ended: an operation where its first element and its count
292
+ ; make one, and a list otherwise.
293
+ def expr-array [items]
294
+ let [list (expr-list items)]
295
+ match items
296
+ case [[_ _ _ "("]] ["()" :atom list :none]
297
+ case [[_ _ _ "("] x] [(string-join "" ["(" (expr-text x) ")"]) :atom list :none]
298
+ case [[_ _ _ "+"] x] [(string-join "" ["+" (expr-term x)]) :atom list :none]
299
+ case [[_ _ _ "-"] x] [(string-join "" ["-" (expr-term x)]) :atom list :none]
300
+ case [[_ _ _ op] a b] (expr-infix op a b list)
301
+ case _ [list :atom list :none]
302
+
303
+ ; The top marker, or :none before the root value.
304
+ def expr-top [s]
305
+ match (count s)
306
+ case 0 :none
307
+ case _ (top s)
308
+
309
+ def expr-mark [marker s]
310
+ push marker (pop s)
311
+
312
+ ; A value written where it is read: at the root, which then is done, in
313
+ ; a list written as it is read, after a comma unless it is the first,
314
+ ; or as an object's member, whose next key is then due. \`text\` is what
315
+ ; the value writes now, and \`inner\` the marker it opens, or :none.
316
+ def expr-stream-value [s text inner]
317
+ match (expr-top s)
318
+ case :none (expr-push-or-done [] inner [text])
319
+ case [:list :stream first]
320
+ match first
321
+ case true (expr-push-or-done (expr-mark [:list :stream false] s) inner [text])
322
+ case false (expr-push-or-done (expr-mark [:list :stream false] s) inner ["," text])
323
+ case [:map :stream :value] (expr-push-or-done (expr-mark [:map :stream false :key] s) inner [text])
324
+
325
+ def expr-push-or-done [s inner out]
326
+ match inner
327
+ case :none
328
+ match (count s)
329
+ case 0 (transition [[:done]] (push "\\n" out))
330
+ case _ (transition s out)
331
+ case _ (transition (push inner s) out)
332
+
333
+ ; A value completed inside a held list or object: its item is added.
334
+ def expr-hold-value [s item]
335
+ match (expr-top s)
336
+ case [:list :hold items] (transition (expr-mark [:list :hold (push (expr-lead items item) items)] s) [])
337
+ case [:map :hold parts [:value k]] (transition (expr-mark [:map :hold (push (string-join "" [k ":" (expr-text item)]) parts) :key] s) [])
338
+
339
+ ; A held value has ended: it is added to the held container around it,
340
+ ; decides the list it is the first element of, or is written where
341
+ ; values are written as they are read, where what led it (a comma, a
342
+ ; key) was written when it began.
343
+ def expr-held [s item]
344
+ match (expr-top s)
345
+ case [:list :hold _] (expr-hold-value s item)
346
+ case [:map :hold _ _] (expr-hold-value s item)
347
+ case [:list :first] (expr-first-held s item)
348
+ case _ (expr-closed s (expr-text item))
349
+
350
+ ; A list whose first element, an object, has ended: one that describes
351
+ ; an operator makes the list an operation, held as one whose first
352
+ ; element is that operator's text is; any other is written, the list's
353
+ ; \`[\` first, and the rest of the list as it is read.
354
+ def expr-first-held [s item]
355
+ match item
356
+ case [text _ _ :none] (transition (expr-mark [:list :stream false] s) ["[" text])
357
+ case _ (transition (expr-mark [:list :hold [(expr-lead [] item)]] s) [])
358
+
359
+ ; Whether the top of the stack holds what is read.
360
+ def expr-holding [s]
361
+ match (expr-top s)
362
+ case [:list :hold _] true
363
+ case [:map :hold _ _] true
364
+ case [:list :first] true
365
+ case _ false
366
+
367
+ ; A list whose first element is due decides on that element's first
368
+ ; event, and answers its marker and the text it writes: one whose first
369
+ ; element is a default operator may be an operation and is held; one
370
+ ; whose first element is an object waits for the object, which may
371
+ ; describe an operator; any other is written as it is read, \`[\` first,
372
+ ; unless it is inside a held one.
373
+ def expr-decide [s mode event]
374
+ match [mode event]
375
+ case [:stream (scalar value)]
376
+ match (expr-op value)
377
+ case :none [(expr-mark [:list :stream true] s) "["]
378
+ case _ [(expr-mark [:list :hold []] s) ""]
379
+ case [:stream object-start] [(expr-mark [:list :first] s) ""]
380
+ case [:stream _] [(expr-mark [:list :stream true] s) "["]
381
+ case _ [(expr-mark [:list :hold []] s) ""]
382
+
383
+ ; A value where one is due: a scalar, or a container's start.
384
+ def expr-value [s event]
385
+ match (expr-holding s)
386
+ case true
387
+ match event
388
+ case (scalar value) (expr-hold-value s (expr-scalar-item value))
389
+ case object-start (transition (push [:map :hold [] :key] s) [])
390
+ case array-start (transition (push [:pending :hold] s) [])
391
+ case false
392
+ match event
393
+ case (scalar value) (expr-stream-value s (expr-scalar-text value) :none)
394
+ case object-start (expr-stream-value s "{" [:map :stream true :key])
395
+ case array-start (expr-stream-value s "" [:pending :stream])
396
+
397
+ ; The first element of a list, once the list has decided: what the
398
+ ; decision writes, then what the element does.
399
+ def expr-first [s mode event]
400
+ match (expr-decide s mode event)
401
+ case [s2 lead]
402
+ match (expr-value s2 event)
403
+ case (transition s3 []) (transition s3 [lead])
404
+ case (transition s3 [text]) (transition s3 [(string-join "" [lead text])])
405
+
406
+ ; A container ends; a held one's item is added or written.
407
+ def expr-close [s marker]
408
+ match marker
409
+ case [:pending :stream] (expr-closed (pop s) "[]")
410
+ case [:pending :hold] (expr-held (pop s) ["[]" :atom "[]" :none])
411
+ case [:list :stream _] (expr-closed (pop s) "]")
412
+ case [:list :hold items] (expr-held (pop s) (expr-array items))
413
+
414
+ ; A container written as it was read has ended: its closing text, and
415
+ ; the document's end at the root.
416
+ def expr-closed [s text]
417
+ match (count s)
418
+ case 0 (transition [[:done]] [text "\\n"])
419
+ case _ (transition s [text])
420
+
421
+ def expr-key [s name]
422
+ match (expr-top s)
423
+ case [:map :stream first :key]
424
+ match first
425
+ case true (transition (expr-mark [:map :stream :value] s) [(quoted name) ":"])
426
+ case false (transition (expr-mark [:map :stream :value] s) ["," (quoted name) ":"])
427
+ case [:map :hold parts :key] (transition (expr-mark [:map :hold parts [:value (quoted name)]] s) [])
428
+ case _ (fail :protocol-order "the events hold a key where a value is due, which a tree's never do")
429
+
430
+ def expr-object-end [s]
431
+ match (expr-top s)
432
+ case [:map :stream _ :key] (expr-closed (pop s) "}")
433
+ case [:map :hold parts :key]
434
+ let [text (string-join "" ["{" (string-join "," parts) "}"])]
435
+ expr-held (pop s) [text :atom text (expr-described parts)]
436
+ case _ (fail :protocol-order "the events end an object that is not open, or one whose member has no value, which a tree's never do")
437
+
438
+ def expr-array-end [s]
439
+ match (expr-top s)
440
+ case [:pending mode] (expr-close s [:pending mode])
441
+ case [:list mode x] (expr-close s [:list mode x])
442
+ case _ (fail :protocol-order "the events end an array that is not open, which a tree's never do")
443
+
444
+ ; Where a value is due: at the root, in a list, or after a key.
445
+ def expr-due [s event]
446
+ match (expr-top s)
447
+ case :none (expr-value s event)
448
+ case [:pending mode] (expr-first s mode event)
449
+ case [:list _ _] (expr-value s event)
450
+ case [:map :stream :value] (expr-value s event)
451
+ case [:map :hold _ [:value _]] (expr-value s event)
452
+ case _ (fail :protocol-order "the events hold a value where a key is due, which a tree's never do")
453
+
454
+ def expr-step [s event]
455
+ match (expr-top s)
456
+ case [:done] (fail :protocol-order "the events hold more after the root value, which a tree's never do")
457
+ case _
458
+ match event
459
+ case (key name) (expr-key s name)
460
+ case object-end (expr-object-end s)
461
+ case array-end (expr-array-end s)
462
+ case _ (expr-due s event)
463
+
464
+ def expr-finish [s]
465
+ match (expr-top s)
466
+ case [:done] []
467
+ case :none (fail :protocol-order "the events hold no value, where a tree's hold one")
468
+ case _ (fail :protocol-order "the events ended inside a container, which a tree's never do")
469
+
470
+ ; The render: a tree's events in, the expression document's text out.
471
+ def expr-render [input]
472
+ join ""
473
+ scan-emit [] expr-step expr-finish (events input)
474
+ ` })
475
+ });
476
+ function translate() {
477
+ return TRANSLATION;
478
+ }
479
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../src/translate.ts"],"names":[],"mappings":";AAAA,yDAAyD;;QA8evD,SAAS;AA7dX,MAAM,WAAW,GAAqB,MAAM,CAAC,MAAM,CAAC;IAClD,QAAQ,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0CX;IACC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8BrD,EAAE,CAAC;IACF,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0YvD,EAAE,CAAC;CACH,CAAC,CAAA;AAEF,SAAS,SAAS;IAChB,OAAO,WAAW,CAAA;AACpB,CAAC"}
@@ -1 +1 @@
1
- {"version":"7.0.2","root":["../src/expr.ts"],"packageJsons":["../node_modules/@tabnas/parser/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/parser/dist/package.json"]}
1
+ {"version":"7.0.2","root":["../src/expr.ts","../src/translate.ts"],"packageJsons":["../node_modules/@tabnas/parser/package.json","../package.json"],"missingPackageJsons":["../node_modules/@tabnas/parser/dist/package.json"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tabnas/expr",
3
- "version": "0.5.15",
3
+ "version": "0.5.17",
4
4
  "description": "This plugin allows the [Tabnas](https://github.com/tabnas/jsonic) JSON parser to support expression syntax.",
5
5
  "main": "dist/expr.js",
6
6
  "type": "commonjs",
@@ -25,7 +25,8 @@
25
25
  "pretest-some": "npm run build",
26
26
  "test-some": "node --enable-source-maps --test-name-pattern=\"$npm_config_pattern\" --test dist-test/**/*.test.js",
27
27
  "watch": "tsc --build src test -w",
28
- "build": "tsc --build src && tsc --build test",
28
+ "embed": "node embed-translate.js",
29
+ "build": "npm run embed && tsc --build src && tsc --build test",
29
30
  "clean": "rm -rf node_modules yarn.lock package-lock.json",
30
31
  "reset": "npm run clean && npm i && npm run build && npm test",
31
32
  "repo-tag": "REPO_VERSION=`node -e \"console.log(require('./package').version)\"` && echo TAG: ts/v$REPO_VERSION && git commit -a -m v$REPO_VERSION && git push && git tag ts/v$REPO_VERSION && git push origin ts/v$REPO_VERSION;",
package/src/expr.ts CHANGED
@@ -1646,16 +1646,67 @@ function p(node: any, seen?: WeakSet<any>): any {
1646
1646
  }
1647
1647
 
1648
1648
 
1649
+ // Reduce a parsed value to the plain S-expression form the shared
1650
+ // fixtures compare and the translation parts are written against:
1651
+ // `1+2*3` reads as `['+', 1, ['*', 2, 3]]`. An operation is an array whose
1652
+ // head is the operator description. The head becomes the operator's
1653
+ // source text (`src`, which for a paren operator is its opening source),
1654
+ // and a term never filled, `undefined`, is dropped. Every other array and
1655
+ // object, an empty one included, is reduced member by member into a new
1656
+ // value, so the result shares no container with the parse, and anything
1657
+ // else is returned as it is.
1658
+ //
1659
+ // A node that contains itself, which a rewrite can leave behind, reads as
1660
+ // '[CIRCLE]' rather than recursing forever. Only a node inside itself
1661
+ // does: a node reached twice by two paths is reduced both times. The test
1662
+ // helper this replaces marked any node it had seen before, and the two
1663
+ // agree on every row of the shared fixtures, where no parse shares a
1664
+ // node; this reading is the Rust port's.
1665
+ //
1666
+ // The Rust port's `simplify`, and the Go port's `Simplify` and its
1667
+ // order-keeping `SimplifyOrdered`, give the same value, and the shared
1668
+ // fixtures hold them to it.
1669
+ function simplify(value: any): any {
1670
+ const open = new Set<any>()
1671
+ const reduce = (node: any): any => {
1672
+ if (null == node || 'object' !== typeof node) return node
1673
+ if (open.has(node)) return '[CIRCLE]'
1674
+ open.add(node)
1675
+ const out = Array.isArray(node)
1676
+ ? (0 === node.length ? [] : [
1677
+ null != node[0] && node[0].src ? node[0].src : reduce(node[0]),
1678
+ ...node.slice(1).map(reduce),
1679
+ ].filter((term: any) => undefined !== term))
1680
+ : omap(node, ([name, member]: [any, any]) => [name, reduce(member)])
1681
+ open.delete(node)
1682
+ return out
1683
+ }
1684
+ return reduce(value)
1685
+ }
1686
+
1687
+
1688
+ // Parse `src` on an instance with the plugin installed, and `simplify`
1689
+ // the result. The Rust port's `parse_simplified`.
1690
+ function parseSimplified(tn: Tabnas, src: string, meta?: any): any {
1691
+ return simplify(tn.parse(src, meta))
1692
+ }
1693
+
1694
+
1649
1695
  const testing = {
1650
1696
  prattify,
1651
1697
  opify: (x: any) => ((x.OP_MARK = OP_MARK), x),
1652
1698
  }
1653
1699
 
1654
- export { VERSION, Expr, evaluation, testing }
1655
-
1656
- export type { ExprOptions, OpDef, Op, Evaluate }
1657
-
1658
1700
  // VERSION is this package's version. It MUST equal package.json "version":
1659
1701
  // the release orchestrator rewrites both, and the version test fails the
1660
1702
  // build if they drift. Mirrors `const VERSION` in go/expr.go.
1661
- const VERSION = '0.5.15'
1703
+ const VERSION = '0.5.17'
1704
+
1705
+ export { VERSION, Expr, evaluation, simplify, parseSimplified, testing }
1706
+
1707
+ export type { ExprOptions, OpDef, Op, Evaluate }
1708
+
1709
+ // The translation parts a host composes a translation from (admin ADR-27):
1710
+ // the manifest and the alchemy sources, generated by ../embed-translate.js.
1711
+ export { translate } from './translate'
1712
+ export type { TranslationPart, TranslationParts } from './translate'