@creationix/rex 0.1.4 → 0.3.1

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/rex.js CHANGED
@@ -33,38 +33,8 @@ var OPCODE_IDS = {
33
33
  mod: 20,
34
34
  neg: 21
35
35
  };
36
- var registeredDomainRefs = {};
37
- function registerDomainExtensionRef(name, refId = 0) {
38
- if (!name)
39
- throw new Error("Domain extension name cannot be empty");
40
- if (!Number.isInteger(refId) || refId < 0)
41
- throw new Error(`Invalid domain extension ref id for '${name}': ${refId}`);
42
- registeredDomainRefs[name] = refId;
43
- }
44
- function registerDomainExtensionRefs(refs) {
45
- for (const [name, refId] of Object.entries(refs)) {
46
- registerDomainExtensionRef(name, refId);
47
- }
48
- }
49
- function clearDomainExtensionRefs() {
50
- for (const name of Object.keys(registeredDomainRefs))
51
- delete registeredDomainRefs[name];
52
- }
53
- function getRegisteredDomainExtensionRefs() {
54
- return { ...registeredDomainRefs };
55
- }
56
- function resolveDomainRefMap(overrides) {
57
- if (!overrides || Object.keys(overrides).length === 0) {
58
- return Object.keys(registeredDomainRefs).length > 0 ? { ...registeredDomainRefs } : undefined;
59
- }
60
- const merged = { ...registeredDomainRefs };
61
- for (const [name, refId] of Object.entries(overrides)) {
62
- if (!Number.isInteger(refId) || refId < 0)
63
- throw new Error(`Invalid domain extension ref id for '${name}': ${refId}`);
64
- merged[name] = refId;
65
- }
66
- return merged;
67
- }
36
+ var FIRST_NON_RESERVED_REF = 8;
37
+ var DOMAIN_DIGIT_INDEX = new Map(Array.from(DIGITS).map((char, index) => [char, index]));
68
38
  var BINARY_TO_OPCODE = {
69
39
  add: "add",
70
40
  sub: "sub",
@@ -177,12 +147,33 @@ function encodeBareOrLengthString(value) {
177
147
  return `${value}:`;
178
148
  return `${encodeUint(byteLength(value))},${value}`;
179
149
  }
150
+ var DEC_PARTS = /^(-?\d)(?:\.(\d+))?e([+-]\d+)$/;
151
+ function splitDecimal(num) {
152
+ const match = num.toExponential().match(DEC_PARTS);
153
+ if (!match)
154
+ throw new Error(`Failed to split decimal for ${num}`);
155
+ const [, b1, b2 = "", e1] = match;
156
+ const base = Number.parseInt(b1 + b2, 10);
157
+ const exp = Number.parseInt(e1, 10) - b2.length;
158
+ return { base, exp };
159
+ }
160
+ function encodeDecimal(significand, power) {
161
+ return `${encodeZigzag(power)}*${encodeInt(significand)}`;
162
+ }
180
163
  function encodeNumberNode(node) {
181
164
  const numberValue = node.value;
182
- if (!Number.isFinite(numberValue))
183
- throw new Error(`Cannot encode non-finite number: ${node.raw}`);
184
- if (Number.isInteger(numberValue))
185
- return encodeInt(numberValue);
165
+ if (Number.isNaN(numberValue))
166
+ return "5'";
167
+ if (numberValue === Infinity)
168
+ return "6'";
169
+ if (numberValue === -Infinity)
170
+ return "7'";
171
+ if (Number.isInteger(numberValue)) {
172
+ const { base, exp } = splitDecimal(numberValue);
173
+ if (exp >= 0 && exp <= 4)
174
+ return encodeInt(numberValue);
175
+ return encodeDecimal(base, exp);
176
+ }
186
177
  const raw = node.raw.toLowerCase();
187
178
  const sign = raw.startsWith("-") ? -1 : 1;
188
179
  const unsigned = sign < 0 ? raw.slice(1) : raw;
@@ -205,7 +196,7 @@ function encodeNumberNode(node) {
205
196
  significand /= 10;
206
197
  power += 1;
207
198
  }
208
- return `${encodeZigzag(power)}*${encodeInt(significand)}`;
199
+ return encodeDecimal(significand, power);
209
200
  }
210
201
  function encodeOpcode(opcode) {
211
202
  return `${encodeUint(OPCODE_IDS[opcode])}%`;
@@ -217,13 +208,13 @@ function needsOptionalPrefix(encoded) {
217
208
  const first = encoded[0];
218
209
  if (!first)
219
210
  return false;
220
- return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<";
211
+ return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<" || first === "#";
221
212
  }
222
213
  function addOptionalPrefix(encoded) {
223
214
  if (!needsOptionalPrefix(encoded))
224
215
  return encoded;
225
216
  let payload = encoded;
226
- if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(")) {
217
+ if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
227
218
  payload = encoded.slice(2, -1);
228
219
  } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
229
220
  payload = encoded.slice(2, -1);
@@ -254,6 +245,33 @@ function encodeConditionalElse(elseBranch) {
254
245
  return encodeNode(nested);
255
246
  }
256
247
  function encodeNavigation(node) {
248
+ const domainRefs = activeEncodeOptions?.domainRefs;
249
+ if (domainRefs && node.target.type === "identifier") {
250
+ const staticPath = [node.target.name];
251
+ for (const segment of node.segments) {
252
+ if (segment.type !== "static")
253
+ break;
254
+ staticPath.push(segment.key);
255
+ }
256
+ for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
257
+ const dottedName = staticPath.slice(0, pathLength).join(".");
258
+ const domainRef = domainRefs[dottedName];
259
+ if (domainRef === undefined)
260
+ continue;
261
+ const consumedStaticSegments = pathLength - 1;
262
+ if (consumedStaticSegments === node.segments.length) {
263
+ return `${encodeUint(domainRef)}'`;
264
+ }
265
+ const parts2 = [`${encodeUint(domainRef)}'`];
266
+ for (const segment of node.segments.slice(consumedStaticSegments)) {
267
+ if (segment.type === "static")
268
+ parts2.push(encodeBareOrLengthString(segment.key));
269
+ else
270
+ parts2.push(encodeNode(segment.key));
271
+ }
272
+ return encodeCallParts(parts2);
273
+ }
274
+ }
257
275
  const parts = [encodeNode(node.target)];
258
276
  for (const segment of node.segments) {
259
277
  if (segment.type === "static")
@@ -263,6 +281,11 @@ function encodeNavigation(node) {
263
281
  }
264
282
  return encodeCallParts(parts);
265
283
  }
284
+ function encodeWhile(node) {
285
+ const cond = encodeNode(node.condition);
286
+ const body = addOptionalPrefix(encodeBlockExpression(node.body));
287
+ return `#(${cond}${body})`;
288
+ }
266
289
  function encodeFor(node) {
267
290
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
268
291
  if (node.binding.type === "binding:expr") {
@@ -399,6 +422,8 @@ function encodeNode(node) {
399
422
  }
400
423
  case "for":
401
424
  return encodeFor(node);
425
+ case "while":
426
+ return encodeWhile(node);
402
427
  case "break":
403
428
  return ";";
404
429
  case "continue":
@@ -473,6 +498,18 @@ function isPlainObject(value) {
473
498
  function isBareKeyName(key) {
474
499
  return /^[A-Za-z_][A-Za-z0-9_-]*$/.test(key);
475
500
  }
501
+ function isNumericKey(key) {
502
+ if (key === "")
503
+ return false;
504
+ return String(Number(key)) === key && Number.isFinite(Number(key));
505
+ }
506
+ function stringifyKey(key) {
507
+ if (isBareKeyName(key))
508
+ return key;
509
+ if (isNumericKey(key))
510
+ return key;
511
+ return stringifyString(key);
512
+ }
476
513
  function stringifyString(value) {
477
514
  return JSON.stringify(value);
478
515
  }
@@ -484,8 +521,12 @@ function stringifyInline(value) {
484
521
  if (typeof value === "boolean")
485
522
  return value ? "true" : "false";
486
523
  if (typeof value === "number") {
487
- if (!Number.isFinite(value))
488
- throw new Error("Rex stringify() cannot encode non-finite numbers");
524
+ if (Number.isNaN(value))
525
+ return "nan";
526
+ if (value === Infinity)
527
+ return "inf";
528
+ if (value === -Infinity)
529
+ return "-inf";
489
530
  return String(value);
490
531
  }
491
532
  if (typeof value === "string")
@@ -499,7 +540,7 @@ function stringifyInline(value) {
499
540
  const entries = Object.entries(value);
500
541
  if (entries.length === 0)
501
542
  return "{}";
502
- const body = entries.map(([key, item]) => `${isBareKeyName(key) ? key : stringifyString(key)}: ${stringifyInline(item)}`).join(" ");
543
+ const body = entries.map(([key, item]) => `${stringifyKey(key)}: ${stringifyInline(item)}`).join(" ");
503
544
  return `{${body}}`;
504
545
  }
505
546
  throw new Error(`Rex stringify() cannot encode value of type ${typeof value}`);
@@ -536,7 +577,7 @@ ${indent}]`;
536
577
  if (entries.length === 0)
537
578
  return "{}";
538
579
  const lines = entries.map(([key, item]) => {
539
- const keyText = isBareKeyName(key) ? key : stringifyString(key);
580
+ const keyText = stringifyKey(key);
540
581
  const rendered = stringifyPretty(item, depth + 1, indentSize, maxWidth);
541
582
  return `${childIndent}${keyText}: ${rendered}`;
542
583
  });
@@ -550,6 +591,86 @@ ${indent}}`;
550
591
  function parse(source) {
551
592
  return parseDataNode(parseToIR(source));
552
593
  }
594
+ function domainRefsFromConfig(config) {
595
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
596
+ throw new Error("Domain config must be an object");
597
+ }
598
+ const refs = {};
599
+ for (const section of Object.values(config)) {
600
+ if (!section || typeof section !== "object" || Array.isArray(section))
601
+ continue;
602
+ mapConfigEntries(section, refs);
603
+ }
604
+ return refs;
605
+ }
606
+ function decodeDomainRefKey(refText) {
607
+ if (!refText)
608
+ throw new Error("Domain ref key cannot be empty");
609
+ if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
610
+ throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
611
+ }
612
+ if (refText.length > 1 && refText[0] === "0") {
613
+ throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
614
+ }
615
+ if (/^[1-9]$/.test(refText)) {
616
+ throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
617
+ }
618
+ let value = 0;
619
+ for (const char of refText) {
620
+ const digit = DOMAIN_DIGIT_INDEX.get(char);
621
+ if (digit === undefined)
622
+ throw new Error(`Invalid domain ref key '${refText}'`);
623
+ value = value * 64 + digit;
624
+ if (value > Number.MAX_SAFE_INTEGER) {
625
+ throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
626
+ }
627
+ }
628
+ if (value < FIRST_NON_RESERVED_REF) {
629
+ throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
630
+ }
631
+ return value;
632
+ }
633
+ function mapConfigEntries(entries, refs) {
634
+ const sourceKindByRoot = new Map;
635
+ for (const root of Object.keys(refs)) {
636
+ sourceKindByRoot.set(root, "explicit");
637
+ }
638
+ for (const [refText, rawEntry] of Object.entries(entries)) {
639
+ const entry = rawEntry;
640
+ if (!entry || typeof entry !== "object")
641
+ continue;
642
+ if (!Array.isArray(entry.names))
643
+ continue;
644
+ const refId = decodeDomainRefKey(refText);
645
+ for (const rawName of entry.names) {
646
+ if (typeof rawName !== "string")
647
+ continue;
648
+ const existingNameRef = refs[rawName];
649
+ if (existingNameRef !== undefined && existingNameRef !== refId) {
650
+ throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
651
+ }
652
+ refs[rawName] = refId;
653
+ const root = rawName.split(".")[0];
654
+ if (!root)
655
+ continue;
656
+ const currentKind = rawName.includes(".") ? "implicit" : "explicit";
657
+ const existing = refs[root];
658
+ if (existing !== undefined) {
659
+ if (existing === refId)
660
+ continue;
661
+ const existingKind = sourceKindByRoot.get(root) ?? "explicit";
662
+ if (currentKind === "explicit") {
663
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
664
+ }
665
+ if (existingKind === "explicit")
666
+ continue;
667
+ continue;
668
+ }
669
+ refs[root] = refId;
670
+ sourceKindByRoot.set(root, currentKind);
671
+ }
672
+ }
673
+ }
553
674
  function stringify(value, options) {
554
675
  const indent = options?.indent ?? 2;
555
676
  const maxWidth = options?.maxWidth ?? 80;
@@ -1116,7 +1237,16 @@ function inlineAdjacentPureAssignments(block) {
1116
1237
  return out;
1117
1238
  }
1118
1239
  function toNumberNode(value) {
1119
- return { type: "number", raw: String(value), value };
1240
+ let raw;
1241
+ if (Number.isNaN(value))
1242
+ raw = "nan";
1243
+ else if (value === Infinity)
1244
+ raw = "inf";
1245
+ else if (value === -Infinity)
1246
+ raw = "-inf";
1247
+ else
1248
+ raw = String(value);
1249
+ return { type: "number", raw, value };
1120
1250
  }
1121
1251
  function toStringNode(value) {
1122
1252
  return { type: "string", raw: JSON.stringify(value) };
@@ -1128,7 +1258,7 @@ function toLiteralNode(value) {
1128
1258
  return { type: "null" };
1129
1259
  if (typeof value === "boolean")
1130
1260
  return { type: "boolean", value };
1131
- if (typeof value === "number" && Number.isFinite(value))
1261
+ if (typeof value === "number")
1132
1262
  return toNumberNode(value);
1133
1263
  if (typeof value === "string")
1134
1264
  return toStringNode(value);
@@ -1440,12 +1570,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1440
1570
  if (conditionValue !== undefined || condition.type === "undefined") {
1441
1571
  const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1442
1572
  if (passes) {
1443
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1444
- if (thenBlock.length === 0)
1573
+ const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1574
+ if (thenBlock2.length === 0)
1445
1575
  return { type: "undefined" };
1446
- if (thenBlock.length === 1)
1447
- return thenBlock[0];
1448
- return { type: "program", body: thenBlock };
1576
+ if (thenBlock2.length === 1)
1577
+ return thenBlock2[0];
1578
+ return { type: "program", body: thenBlock2 };
1449
1579
  }
1450
1580
  if (!node.elseBranch)
1451
1581
  return { type: "undefined" };
@@ -1467,12 +1597,26 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1467
1597
  elseBranch: loweredElse.elseBranch
1468
1598
  };
1469
1599
  }
1600
+ const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1601
+ const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1602
+ let finalCondition = condition;
1603
+ if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1604
+ const name = condition.place.name;
1605
+ const reads = new Set;
1606
+ for (const part of thenBlock)
1607
+ collectReads(part, reads);
1608
+ if (elseBranch)
1609
+ collectReadsElse(elseBranch, reads);
1610
+ if (!reads.has(name)) {
1611
+ finalCondition = condition.value;
1612
+ }
1613
+ }
1470
1614
  return {
1471
1615
  type: "conditional",
1472
1616
  head: node.head,
1473
- condition,
1474
- thenBlock: optimizeBlock(node.thenBlock, thenEnv, currentDepth),
1475
- elseBranch: optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth)
1617
+ condition: finalCondition,
1618
+ thenBlock,
1619
+ elseBranch
1476
1620
  };
1477
1621
  }
1478
1622
  case "for": {
@@ -1927,13 +2071,20 @@ function compile(source, options) {
1927
2071
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
1928
2072
  if (options?.minifyNames)
1929
2073
  lowered = minifyLocalNamesIR(lowered);
2074
+ const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1930
2075
  return encodeIR(lowered, {
1931
- domainRefs: resolveDomainRefMap(options?.domainRefs),
2076
+ domainRefs,
1932
2077
  dedupeValues: options?.dedupeValues,
1933
2078
  dedupeMinBytes: options?.dedupeMinBytes
1934
2079
  });
1935
2080
  }
1936
2081
  function parseNumber(raw) {
2082
+ if (raw === "nan")
2083
+ return NaN;
2084
+ if (raw === "inf")
2085
+ return Infinity;
2086
+ if (raw === "-inf")
2087
+ return -Infinity;
1937
2088
  if (/^-?0x/i.test(raw))
1938
2089
  return parseInt(raw, 16);
1939
2090
  if (/^-?0b/i.test(raw)) {
@@ -2158,6 +2309,13 @@ semantics.addOperation("toIR", {
2158
2309
  return body[0];
2159
2310
  return { type: "program", body };
2160
2311
  },
2312
+ WhileExpr(_while, condition, _do, block, _end) {
2313
+ return {
2314
+ type: "while",
2315
+ condition: condition.toIR(),
2316
+ body: block.toIR()
2317
+ };
2318
+ },
2161
2319
  ForExpr(_for, binding, _do, block, _end) {
2162
2320
  return {
2163
2321
  type: "for",
@@ -2304,16 +2462,13 @@ var rex_default = semantics;
2304
2462
  export {
2305
2463
  stringify,
2306
2464
  semantics,
2307
- registerDomainExtensionRefs,
2308
- registerDomainExtensionRef,
2309
2465
  parseToIR,
2310
2466
  parse,
2311
2467
  optimizeIR,
2312
2468
  minifyLocalNamesIR,
2313
2469
  grammar,
2314
- getRegisteredDomainExtensionRefs,
2315
2470
  encodeIR,
2471
+ domainRefsFromConfig,
2316
2472
  rex_default as default,
2317
- compile,
2318
- clearDomainExtensionRefs
2473
+ compile
2319
2474
  };
package/rex.ohm CHANGED
@@ -38,7 +38,7 @@ Rex {
38
38
  | UnaryExpr -- value
39
39
 
40
40
  UnaryExpr
41
- = "-" ~digit UnaryExpr -- neg
41
+ = "-" ~digit ~infTok UnaryExpr -- neg
42
42
  | "~" UnaryExpr -- not
43
43
  | DeleteKw Place -- delete
44
44
  | PostfixExpr -- value
@@ -47,7 +47,7 @@ Rex {
47
47
  = PrimaryExpr PostfixTail* -- chain
48
48
 
49
49
  PostfixTail
50
- = "." keyName -- navStatic
50
+ = "." navKey -- navStatic
51
51
  | ".(" Expr ")" -- navDynamic
52
52
  | "(" ")" -- callEmpty
53
53
  | "(" Elements<Expr> ")" -- call
@@ -56,6 +56,7 @@ Rex {
56
56
  = ConditionalExpr
57
57
  | DoExpr
58
58
  | ForExpr
59
+ | WhileExpr
59
60
  | BreakKw -- break
60
61
  | ContinueKw -- continue
61
62
  | Array
@@ -87,7 +88,7 @@ Rex {
87
88
  | SelfKw
88
89
 
89
90
  PlaceTail
90
- = "." keyName -- navStatic
91
+ = "." navKey -- navStatic
91
92
  | ".(" Expr ")" -- navDynamic
92
93
 
93
94
  ConditionalExpr
@@ -104,6 +105,9 @@ Rex {
104
105
  ForExpr
105
106
  = ForKw BindingExpr DoKw Block EndKw
106
107
 
108
+ WhileExpr
109
+ = WhileKw Expr DoKw Block EndKw
110
+
107
111
  DoExpr
108
112
  = DoKw Block EndKw
109
113
 
@@ -138,6 +142,10 @@ Rex {
138
142
  | String -- string
139
143
  | "(" Expr ")" -- computed
140
144
 
145
+ navKey
146
+ = keyName
147
+ | digit+
148
+
141
149
  keyName
142
150
  = nameHead nameTail*
143
151
 
@@ -181,10 +189,13 @@ Rex {
181
189
  | falseTok
182
190
  | nullTok
183
191
  | undefinedTok
192
+ | nanTok
193
+ | infTok
184
194
  | selfTok
185
195
  | whenTok
186
196
  | unlessTok
187
197
  | forTok
198
+ | whileTok
188
199
  | inTok
189
200
  | ofTok
190
201
  | doTok
@@ -222,6 +233,9 @@ Rex {
222
233
  UnlessKw
223
234
  = unlessTok
224
235
 
236
+ WhileKw
237
+ = whileTok
238
+
225
239
  ForKw
226
240
  = forTok
227
241
 
@@ -276,6 +290,12 @@ Rex {
276
290
  undefinedTok
277
291
  = "undefined" ~nameTail
278
292
 
293
+ nanTok
294
+ = "nan" ~nameTail
295
+
296
+ infTok
297
+ = "inf" ~nameTail
298
+
279
299
  selfTok
280
300
  = "self" ~nameTail
281
301
 
@@ -285,6 +305,9 @@ Rex {
285
305
  unlessTok
286
306
  = "unless" ~nameTail
287
307
 
308
+ whileTok
309
+ = "while" ~nameTail
310
+
288
311
  forTok
289
312
  = "for" ~nameTail
290
313
 
@@ -371,8 +394,16 @@ Rex {
371
394
  Number
372
395
  = hexNumber
373
396
  | binaryNumber
397
+ | infLiteral
398
+ | nanLiteral
374
399
  | decimalNumber
375
400
 
401
+ infLiteral
402
+ = "-"? infTok
403
+
404
+ nanLiteral
405
+ = nanTok
406
+
376
407
  hexNumber
377
408
  = "-"? "0x" hex+
378
409