@creationix/rex 0.1.3 → 0.3.0

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 = 5;
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",
@@ -217,13 +187,13 @@ function needsOptionalPrefix(encoded) {
217
187
  const first = encoded[0];
218
188
  if (!first)
219
189
  return false;
220
- return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<";
190
+ return first === "[" || first === "{" || first === "(" || first === "=" || first === "~" || first === "?" || first === "!" || first === "|" || first === "&" || first === ">" || first === "<" || first === "#";
221
191
  }
222
192
  function addOptionalPrefix(encoded) {
223
193
  if (!needsOptionalPrefix(encoded))
224
194
  return encoded;
225
195
  let payload = encoded;
226
- if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(")) {
196
+ if (encoded.startsWith("?(") || encoded.startsWith("!(") || encoded.startsWith("|(") || encoded.startsWith("&(") || encoded.startsWith(">(") || encoded.startsWith("<(") || encoded.startsWith("#(")) {
227
197
  payload = encoded.slice(2, -1);
228
198
  } else if (encoded.startsWith(">[") || encoded.startsWith(">{")) {
229
199
  payload = encoded.slice(2, -1);
@@ -254,6 +224,33 @@ function encodeConditionalElse(elseBranch) {
254
224
  return encodeNode(nested);
255
225
  }
256
226
  function encodeNavigation(node) {
227
+ const domainRefs = activeEncodeOptions?.domainRefs;
228
+ if (domainRefs && node.target.type === "identifier") {
229
+ const staticPath = [node.target.name];
230
+ for (const segment of node.segments) {
231
+ if (segment.type !== "static")
232
+ break;
233
+ staticPath.push(segment.key);
234
+ }
235
+ for (let pathLength = staticPath.length;pathLength >= 1; pathLength -= 1) {
236
+ const dottedName = staticPath.slice(0, pathLength).join(".");
237
+ const domainRef = domainRefs[dottedName];
238
+ if (domainRef === undefined)
239
+ continue;
240
+ const consumedStaticSegments = pathLength - 1;
241
+ if (consumedStaticSegments === node.segments.length) {
242
+ return `${encodeUint(domainRef)}'`;
243
+ }
244
+ const parts2 = [`${encodeUint(domainRef)}'`];
245
+ for (const segment of node.segments.slice(consumedStaticSegments)) {
246
+ if (segment.type === "static")
247
+ parts2.push(encodeBareOrLengthString(segment.key));
248
+ else
249
+ parts2.push(encodeNode(segment.key));
250
+ }
251
+ return encodeCallParts(parts2);
252
+ }
253
+ }
257
254
  const parts = [encodeNode(node.target)];
258
255
  for (const segment of node.segments) {
259
256
  if (segment.type === "static")
@@ -263,6 +260,11 @@ function encodeNavigation(node) {
263
260
  }
264
261
  return encodeCallParts(parts);
265
262
  }
263
+ function encodeWhile(node) {
264
+ const cond = encodeNode(node.condition);
265
+ const body = addOptionalPrefix(encodeBlockExpression(node.body));
266
+ return `#(${cond}${body})`;
267
+ }
266
268
  function encodeFor(node) {
267
269
  const body = addOptionalPrefix(encodeBlockExpression(node.body));
268
270
  if (node.binding.type === "binding:expr") {
@@ -399,6 +401,8 @@ function encodeNode(node) {
399
401
  }
400
402
  case "for":
401
403
  return encodeFor(node);
404
+ case "while":
405
+ return encodeWhile(node);
402
406
  case "break":
403
407
  return ";";
404
408
  case "continue":
@@ -550,6 +554,86 @@ ${indent}}`;
550
554
  function parse(source) {
551
555
  return parseDataNode(parseToIR(source));
552
556
  }
557
+ function domainRefsFromConfig(config) {
558
+ if (!config || typeof config !== "object" || Array.isArray(config)) {
559
+ throw new Error("Domain config must be an object");
560
+ }
561
+ const refs = {};
562
+ for (const section of Object.values(config)) {
563
+ if (!section || typeof section !== "object" || Array.isArray(section))
564
+ continue;
565
+ mapConfigEntries(section, refs);
566
+ }
567
+ return refs;
568
+ }
569
+ function decodeDomainRefKey(refText) {
570
+ if (!refText)
571
+ throw new Error("Domain ref key cannot be empty");
572
+ if (!/^[0-9A-Za-z_-]+$/.test(refText)) {
573
+ throw new Error(`Invalid domain ref key '${refText}' (must use base64 alphabet 0-9a-zA-Z-_)`);
574
+ }
575
+ if (refText.length > 1 && refText[0] === "0") {
576
+ throw new Error(`Invalid domain ref key '${refText}' (leading zeroes are not allowed)`);
577
+ }
578
+ if (/^[1-9]$/.test(refText)) {
579
+ throw new Error(`Invalid domain ref key '${refText}' (reserved by core language)`);
580
+ }
581
+ let value = 0;
582
+ for (const char of refText) {
583
+ const digit = DOMAIN_DIGIT_INDEX.get(char);
584
+ if (digit === undefined)
585
+ throw new Error(`Invalid domain ref key '${refText}'`);
586
+ value = value * 64 + digit;
587
+ if (value > Number.MAX_SAFE_INTEGER) {
588
+ throw new Error(`Invalid domain ref key '${refText}' (must fit in 53 bits)`);
589
+ }
590
+ }
591
+ if (value < FIRST_NON_RESERVED_REF) {
592
+ throw new Error(`Invalid domain ref key '${refText}' (maps to reserved id ${value})`);
593
+ }
594
+ return value;
595
+ }
596
+ function mapConfigEntries(entries, refs) {
597
+ const sourceKindByRoot = new Map;
598
+ for (const root of Object.keys(refs)) {
599
+ sourceKindByRoot.set(root, "explicit");
600
+ }
601
+ for (const [refText, rawEntry] of Object.entries(entries)) {
602
+ const entry = rawEntry;
603
+ if (!entry || typeof entry !== "object")
604
+ continue;
605
+ if (!Array.isArray(entry.names))
606
+ continue;
607
+ const refId = decodeDomainRefKey(refText);
608
+ for (const rawName of entry.names) {
609
+ if (typeof rawName !== "string")
610
+ continue;
611
+ const existingNameRef = refs[rawName];
612
+ if (existingNameRef !== undefined && existingNameRef !== refId) {
613
+ throw new Error(`Conflicting refs for '${rawName}': ${existingNameRef} vs ${refId}`);
614
+ }
615
+ refs[rawName] = refId;
616
+ const root = rawName.split(".")[0];
617
+ if (!root)
618
+ continue;
619
+ const currentKind = rawName.includes(".") ? "implicit" : "explicit";
620
+ const existing = refs[root];
621
+ if (existing !== undefined) {
622
+ if (existing === refId)
623
+ continue;
624
+ const existingKind = sourceKindByRoot.get(root) ?? "explicit";
625
+ if (currentKind === "explicit") {
626
+ throw new Error(`Conflicting refs for '${root}': ${existing} vs ${refId}`);
627
+ }
628
+ if (existingKind === "explicit")
629
+ continue;
630
+ continue;
631
+ }
632
+ refs[root] = refId;
633
+ sourceKindByRoot.set(root, currentKind);
634
+ }
635
+ }
636
+ }
553
637
  function stringify(value, options) {
554
638
  const indent = options?.indent ?? 2;
555
639
  const maxWidth = options?.maxWidth ?? 80;
@@ -1440,12 +1524,12 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1440
1524
  if (conditionValue !== undefined || condition.type === "undefined") {
1441
1525
  const passes = node.head === "when" ? isDefinedValue(conditionValue) : !isDefinedValue(conditionValue);
1442
1526
  if (passes) {
1443
- const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1444
- if (thenBlock.length === 0)
1527
+ const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1528
+ if (thenBlock2.length === 0)
1445
1529
  return { type: "undefined" };
1446
- if (thenBlock.length === 1)
1447
- return thenBlock[0];
1448
- return { type: "program", body: thenBlock };
1530
+ if (thenBlock2.length === 1)
1531
+ return thenBlock2[0];
1532
+ return { type: "program", body: thenBlock2 };
1449
1533
  }
1450
1534
  if (!node.elseBranch)
1451
1535
  return { type: "undefined" };
@@ -1467,12 +1551,26 @@ function optimizeNode(node, env, currentDepth, asPlace = false) {
1467
1551
  elseBranch: loweredElse.elseBranch
1468
1552
  };
1469
1553
  }
1554
+ const thenBlock = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
1555
+ const elseBranch = optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth);
1556
+ let finalCondition = condition;
1557
+ if (condition.type === "assign" && condition.op === "=" && condition.place.type === "identifier") {
1558
+ const name = condition.place.name;
1559
+ const reads = new Set;
1560
+ for (const part of thenBlock)
1561
+ collectReads(part, reads);
1562
+ if (elseBranch)
1563
+ collectReadsElse(elseBranch, reads);
1564
+ if (!reads.has(name)) {
1565
+ finalCondition = condition.value;
1566
+ }
1567
+ }
1470
1568
  return {
1471
1569
  type: "conditional",
1472
1570
  head: node.head,
1473
- condition,
1474
- thenBlock: optimizeBlock(node.thenBlock, thenEnv, currentDepth),
1475
- elseBranch: optimizeElse(node.elseBranch, cloneOptimizeEnv(env), currentDepth)
1571
+ condition: finalCondition,
1572
+ thenBlock,
1573
+ elseBranch
1476
1574
  };
1477
1575
  }
1478
1576
  case "for": {
@@ -1927,8 +2025,9 @@ function compile(source, options) {
1927
2025
  let lowered = options?.optimize ? optimizeIR(ir) : ir;
1928
2026
  if (options?.minifyNames)
1929
2027
  lowered = minifyLocalNamesIR(lowered);
2028
+ const domainRefs = options?.domainConfig ? domainRefsFromConfig(options.domainConfig) : undefined;
1930
2029
  return encodeIR(lowered, {
1931
- domainRefs: resolveDomainRefMap(options?.domainRefs),
2030
+ domainRefs,
1932
2031
  dedupeValues: options?.dedupeValues,
1933
2032
  dedupeMinBytes: options?.dedupeMinBytes
1934
2033
  });
@@ -2158,6 +2257,13 @@ semantics.addOperation("toIR", {
2158
2257
  return body[0];
2159
2258
  return { type: "program", body };
2160
2259
  },
2260
+ WhileExpr(_while, condition, _do, block, _end) {
2261
+ return {
2262
+ type: "while",
2263
+ condition: condition.toIR(),
2264
+ body: block.toIR()
2265
+ };
2266
+ },
2161
2267
  ForExpr(_for, binding, _do, block, _end) {
2162
2268
  return {
2163
2269
  type: "for",
@@ -2304,16 +2410,13 @@ var rex_default = semantics;
2304
2410
  export {
2305
2411
  stringify,
2306
2412
  semantics,
2307
- registerDomainExtensionRefs,
2308
- registerDomainExtensionRef,
2309
2413
  parseToIR,
2310
2414
  parse,
2311
2415
  optimizeIR,
2312
2416
  minifyLocalNamesIR,
2313
2417
  grammar,
2314
- getRegisteredDomainExtensionRefs,
2315
2418
  encodeIR,
2419
+ domainRefsFromConfig,
2316
2420
  rex_default as default,
2317
- compile,
2318
- clearDomainExtensionRefs
2421
+ compile
2319
2422
  };
package/rex.ohm CHANGED
@@ -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
@@ -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
 
@@ -185,6 +189,7 @@ Rex {
185
189
  | whenTok
186
190
  | unlessTok
187
191
  | forTok
192
+ | whileTok
188
193
  | inTok
189
194
  | ofTok
190
195
  | doTok
@@ -222,6 +227,9 @@ Rex {
222
227
  UnlessKw
223
228
  = unlessTok
224
229
 
230
+ WhileKw
231
+ = whileTok
232
+
225
233
  ForKw
226
234
  = forTok
227
235
 
@@ -285,6 +293,9 @@ Rex {
285
293
  unlessTok
286
294
  = "unless" ~nameTail
287
295
 
296
+ whileTok
297
+ = "while" ~nameTail
298
+
288
299
  forTok
289
300
  = "for" ~nameTail
290
301