@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/README.md +62 -62
- package/package.json +7 -6
- package/rex-cli.js +3978 -30
- package/{rex-compile.ts → rex-cli.ts} +200 -206
- package/{rex-compile.js → rex-repl.js} +1576 -195
- package/rex-repl.ts +545 -0
- package/rex.js +151 -48
- package/rex.ohm +11 -0
- package/rex.ohm-bundle.cjs +1 -1
- package/rex.ohm-bundle.d.ts +3 -0
- package/rex.ohm-bundle.js +1 -1
- package/rex.ts +2536 -2422
- package/rexc-interpreter.ts +916 -848
package/rex.js
CHANGED
|
@@ -33,38 +33,8 @@ var OPCODE_IDS = {
|
|
|
33
33
|
mod: 20,
|
|
34
34
|
neg: 21
|
|
35
35
|
};
|
|
36
|
-
var
|
|
37
|
-
|
|
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
|
|
1444
|
-
if (
|
|
1527
|
+
const thenBlock2 = optimizeBlock(node.thenBlock, thenEnv, currentDepth);
|
|
1528
|
+
if (thenBlock2.length === 0)
|
|
1445
1529
|
return { type: "undefined" };
|
|
1446
|
-
if (
|
|
1447
|
-
return
|
|
1448
|
-
return { type: "program", body:
|
|
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
|
|
1475
|
-
elseBranch
|
|
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
|
|
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
|
|