@pieceful/ravel-core 0.2.0 → 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/package.json +1 -1
- package/src/directives.d.ts +2 -2
- package/src/directives.js +15 -2
- package/src/index.d.ts +3 -2
- package/src/index.js +134 -38
package/package.json
CHANGED
package/src/directives.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ export function append(reference: string, source: SourceLocation): unknown;
|
|
|
5
5
|
export function newline(count: number, source: SourceLocation): unknown;
|
|
6
6
|
export function pipe(steps: unknown[], source: SourceLocation): unknown;
|
|
7
7
|
export function pass(steps: unknown[], source: SourceLocation): unknown;
|
|
8
|
-
export function createDirective(name: string, value: unknown, source: SourceLocation): unknown;
|
|
9
|
-
export function aliasDirective(name: string, reference: string, source: SourceLocation):
|
|
8
|
+
export function createDirective(name: string, value: unknown[] | string, source: SourceLocation, document?: string): { kind: "create"; document?: string; name: string; compose?: unknown[]; body?: string; source: SourceLocation };
|
|
9
|
+
export function aliasDirective(name: string, reference: string, source: SourceLocation, document?: string): { kind: "alias"; document?: string; name: string; reference: string; source: SourceLocation };
|
package/src/directives.js
CHANGED
|
@@ -12,5 +12,18 @@ export const pass = (steps, source) => ({ kind: "pass", steps, source });
|
|
|
12
12
|
* document's canonical chunk identity. Alias is intentionally the only
|
|
13
13
|
* directive that may introduce an alternate graph name for an existing node.
|
|
14
14
|
*/
|
|
15
|
-
export const createDirective = (name, value, source) => ({
|
|
16
|
-
|
|
15
|
+
export const createDirective = (name, value, source, document) => ({
|
|
16
|
+
kind: "create",
|
|
17
|
+
...(document === undefined ? {} : { document }),
|
|
18
|
+
name,
|
|
19
|
+
// An array is a compose step list; a string is a chunk body.
|
|
20
|
+
...(Array.isArray(value) ? { compose: value } : { body: String(value ?? "") }),
|
|
21
|
+
source
|
|
22
|
+
});
|
|
23
|
+
export const aliasDirective = (name, reference, source, document) => ({
|
|
24
|
+
kind: "alias",
|
|
25
|
+
...(document === undefined ? {} : { document }),
|
|
26
|
+
name,
|
|
27
|
+
reference,
|
|
28
|
+
source
|
|
29
|
+
});
|
package/src/index.d.ts
CHANGED
|
@@ -104,6 +104,7 @@ export interface ChunkReferenceSyntaxOptions {
|
|
|
104
104
|
referenceAliases?: Record<string, string>;
|
|
105
105
|
}
|
|
106
106
|
export function parseChunk(body: string, source: SourceLocation, options?: ChunkReferenceSyntaxOptions): { nodes: unknown[]; diagnostics: Diagnostic[] };
|
|
107
|
+
export function nowebReferenceAt(body: string, index: number): { authored: string; end: number } | null;
|
|
107
108
|
export function parseDefinitionPipeline(text: string, source: SourceLocation): { pipeline: TransformCall[]; diagnostics: Diagnostic[] };
|
|
108
109
|
export function combineMaps(maps: RavelMap[]): PretransformGraph;
|
|
109
110
|
export function transformGraph(graph: PretransformGraph, options?: {
|
|
@@ -137,8 +138,8 @@ export function append(reference: string, source: SourceLocation): unknown;
|
|
|
137
138
|
export function newline(count: number, source: SourceLocation): unknown;
|
|
138
139
|
export function pipe(steps: unknown[], source: SourceLocation): unknown;
|
|
139
140
|
export function pass(steps: unknown[], source: SourceLocation): unknown;
|
|
140
|
-
export function createDirective(name: string, value: unknown, source: SourceLocation): unknown;
|
|
141
|
-
export function aliasDirective(name: string, reference: string, source: SourceLocation):
|
|
141
|
+
export function createDirective(name: string, value: unknown[] | string, source: SourceLocation, document?: string): { kind: "create"; document?: string; name: string; compose?: unknown[]; body?: string; source: SourceLocation };
|
|
142
|
+
export function aliasDirective(name: string, reference: string, source: SourceLocation, document?: string): { kind: "alias"; document?: string; name: string; reference: string; source: SourceLocation };
|
|
142
143
|
export function parseRavelDirectiveBlock(text: string, options: {
|
|
143
144
|
document: string;
|
|
144
145
|
sourceAt(start: number, end: number): SourceLocation;
|
package/src/index.js
CHANGED
|
@@ -177,18 +177,32 @@ const replaceMappedOnce = (value, search, replacement) => {
|
|
|
177
177
|
);
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
+
// Parsing asks for many positions in the same chunk body, so keep the newline
|
|
181
|
+
// offsets of the most recent text and binary-search them instead of rescanning.
|
|
182
|
+
const lineIndexCache = new Map();
|
|
183
|
+
const lineStartsOf = (text) => {
|
|
184
|
+
let starts = lineIndexCache.get(text);
|
|
185
|
+
if (starts) return starts;
|
|
186
|
+
starts = [];
|
|
187
|
+
for (let i = text.indexOf("\n"); i !== -1; i = text.indexOf("\n", i + 1)) starts.push(i + 1);
|
|
188
|
+
// A handful of entries covers a chunk body and the expressions inside it.
|
|
189
|
+
if (lineIndexCache.size >= 16) lineIndexCache.delete(lineIndexCache.keys().next().value);
|
|
190
|
+
lineIndexCache.set(text, starts);
|
|
191
|
+
return starts;
|
|
192
|
+
};
|
|
193
|
+
|
|
180
194
|
const advance = (start, text, index) => {
|
|
181
|
-
|
|
182
|
-
let
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
column += 1;
|
|
189
|
-
}
|
|
195
|
+
const starts = lineStartsOf(text);
|
|
196
|
+
let low = 0;
|
|
197
|
+
let high = starts.length;
|
|
198
|
+
while (low < high) {
|
|
199
|
+
const middle = (low + high) >> 1;
|
|
200
|
+
if (starts[middle] <= index) low = middle + 1;
|
|
201
|
+
else high = middle;
|
|
190
202
|
}
|
|
191
|
-
return
|
|
203
|
+
return low === 0
|
|
204
|
+
? { line: start.line, column: start.column + index, offset: start.offset + index }
|
|
205
|
+
: { line: start.line + low, column: index - starts[low - 1], offset: start.offset + index };
|
|
192
206
|
};
|
|
193
207
|
|
|
194
208
|
const span = (source, text, start, end) => ({
|
|
@@ -382,9 +396,10 @@ const parseEmitSuffix = (value) => {
|
|
|
382
396
|
return { minor, type, inheritMinor: false };
|
|
383
397
|
};
|
|
384
398
|
|
|
385
|
-
|
|
399
|
+
// `text` is the string that `expressionOffset` and `part` offsets index into.
|
|
400
|
+
const parsePipeStep = (part, text, source, expressionOffset, diagnostics) => {
|
|
386
401
|
const match = /^([a-z][a-z0-9-]*)\s*(?:\((.*)\))?$/s.exec(part.value);
|
|
387
|
-
const location = span(source,
|
|
402
|
+
const location = span(source, text, expressionOffset + part.start, expressionOffset + part.end);
|
|
388
403
|
if (!match) {
|
|
389
404
|
diagnostics.push(diagnostic("RV110", "Malformed pipeline step: " + part.value, location));
|
|
390
405
|
return null;
|
|
@@ -430,7 +445,7 @@ const parsePipeStep = (part, expression, source, expressionOffset, diagnostics)
|
|
|
430
445
|
diagnostics.push(diagnostic("RV121", "ch requires one quoted chunk expression.", location));
|
|
431
446
|
return null;
|
|
432
447
|
}
|
|
433
|
-
const parsed = parseExpression(argumentsValue[0], source, expressionOffset + part.start, diagnostics, { allowDelay: false });
|
|
448
|
+
const parsed = parseExpression(argumentsValue[0], source, expressionOffset + part.start, diagnostics, { allowDelay: false, text });
|
|
434
449
|
if (!parsed) return null;
|
|
435
450
|
return { type: "chunk", expression: argumentsValue[0], value: parsed, source: location };
|
|
436
451
|
}
|
|
@@ -438,7 +453,7 @@ const parsePipeStep = (part, expression, source, expressionOffset, diagnostics)
|
|
|
438
453
|
const argumentsParsed = rawArguments.map((argument) => {
|
|
439
454
|
const value = parseValue(argument.value);
|
|
440
455
|
if (typeof value !== "undefined") return value;
|
|
441
|
-
const nested = parsePipeStep({ value: argument.value, start: part.start + argument.start, end: part.start + argument.end },
|
|
456
|
+
const nested = parsePipeStep({ value: argument.value, start: part.start + argument.start, end: part.start + argument.end }, text, source, expressionOffset, diagnostics);
|
|
442
457
|
return nested?.type === "text" || nested?.type === "chunk"
|
|
443
458
|
? { kind: "ravel-command-argument", command: nested }
|
|
444
459
|
: undefined;
|
|
@@ -450,12 +465,14 @@ const parsePipeStep = (part, expression, source, expressionOffset, diagnostics)
|
|
|
450
465
|
return { type: name === "delay" ? "delay" : "transform", name, arguments: argumentsParsed, source: location };
|
|
451
466
|
};
|
|
452
467
|
|
|
453
|
-
|
|
468
|
+
// `text` is the chunk body that `expressionOffset` indexes into; positions
|
|
469
|
+
// must be computed against it, not against the expression alone.
|
|
470
|
+
const parseExpression = (expression, source, expressionOffset, diagnostics, { allowDelay = true, text = expression } = {}) => {
|
|
454
471
|
const parts = splitTopLevel(expression, "|");
|
|
455
472
|
const reference = parts.shift();
|
|
456
473
|
const target = parseChunkId(reference?.value, { reference: true });
|
|
457
474
|
const pipeline = parts
|
|
458
|
-
.map((part) => parsePipeStep(part,
|
|
475
|
+
.map((part) => parsePipeStep(part, text, source, expressionOffset, diagnostics))
|
|
459
476
|
.filter(Boolean);
|
|
460
477
|
if (!reference || !target) {
|
|
461
478
|
const delay = pipeline.length === 1 && pipeline[0].type === "delay" ? pipeline[0] : null;
|
|
@@ -474,7 +491,7 @@ const parseExpression = (expression, source, expressionOffset, diagnostics, { al
|
|
|
474
491
|
expression: typeof value === "string" ? value : command.expression ?? command.value,
|
|
475
492
|
phase,
|
|
476
493
|
safeSymbol,
|
|
477
|
-
source: span(source,
|
|
494
|
+
source: span(source, text, expressionOffset, expressionOffset + expression.length)
|
|
478
495
|
};
|
|
479
496
|
}
|
|
480
497
|
if (reference?.value === "" && pipeline.length && pipeline[0].type !== "delay" &&
|
|
@@ -483,13 +500,13 @@ const parseExpression = (expression, source, expressionOffset, diagnostics, { al
|
|
|
483
500
|
diagnostics.push(diagnostic("RV121", "delay may only appear as the sole command in _\"|delay(...)\".", pipeline.find((step) => step.type === "delay").source));
|
|
484
501
|
return null;
|
|
485
502
|
}
|
|
486
|
-
return { type: "pipeline", pipeline, source: span(source,
|
|
503
|
+
return { type: "pipeline", pipeline, source: span(source, text, expressionOffset, expressionOffset + expression.length) };
|
|
487
504
|
}
|
|
488
505
|
if (pipeline.some((step) => step.type === "delay")) {
|
|
489
506
|
diagnostics.push(diagnostic("RV121", "delay may only appear as the sole command in _\"|delay(...)\".", pipeline.find((step) => step.type === "delay").source));
|
|
490
507
|
return null;
|
|
491
508
|
}
|
|
492
|
-
diagnostics.push(diagnostic("RV110", "Malformed chunk reference: " + (reference?.value ?? ""), span(source,
|
|
509
|
+
diagnostics.push(diagnostic("RV110", "Malformed chunk reference: " + (reference?.value ?? ""), span(source, text, expressionOffset, expressionOffset + expression.length)));
|
|
493
510
|
return null;
|
|
494
511
|
}
|
|
495
512
|
|
|
@@ -503,7 +520,7 @@ const parseExpression = (expression, source, expressionOffset, diagnostics, { al
|
|
|
503
520
|
reference: reference.value,
|
|
504
521
|
target,
|
|
505
522
|
pipeline,
|
|
506
|
-
source: span(source,
|
|
523
|
+
source: span(source, text, expressionOffset, expressionOffset + expression.length)
|
|
507
524
|
};
|
|
508
525
|
};
|
|
509
526
|
|
|
@@ -532,7 +549,10 @@ export const parseDefinitionPipeline = (text, source) => {
|
|
|
532
549
|
// on continuation lines. The first line is already positioned by the literal
|
|
533
550
|
// text before the reference, so it deliberately remains untouched.
|
|
534
551
|
const continuationIndentAt = (body, index) => {
|
|
535
|
-
|
|
552
|
+
// Walk back only to the start of this line; lastIndexOf for an absent "\r"
|
|
553
|
+
// would rescan the whole body for every reference.
|
|
554
|
+
let lineStart = index;
|
|
555
|
+
while (lineStart > 0 && body[lineStart - 1] !== "\n" && body[lineStart - 1] !== "\r") lineStart -= 1;
|
|
536
556
|
return /^[\t ]*/.exec(body.slice(lineStart, index))[0];
|
|
537
557
|
};
|
|
538
558
|
|
|
@@ -577,6 +597,26 @@ const nowebExpression = (value, options) => {
|
|
|
577
597
|
: canonicalName + " | " + value.slice(pipe + 1).trim();
|
|
578
598
|
};
|
|
579
599
|
|
|
600
|
+
/**
|
|
601
|
+
* Recognize a noweb-style `<<name>>` reference starting at `index`.
|
|
602
|
+
*
|
|
603
|
+
* A reference must close on the same line and its name must be non-empty
|
|
604
|
+
* without leading or trailing whitespace, so shift operators such as
|
|
605
|
+
* `std::cout << x` or `a << 2; b >> 1` remain ordinary code. Returns the
|
|
606
|
+
* authored text and the offset of the closing `>>`, or null.
|
|
607
|
+
*/
|
|
608
|
+
const countedReferencePattern = /\\([1-9][0-9]*)_(["'`])/y;
|
|
609
|
+
|
|
610
|
+
export const nowebReferenceAt = (body, index) => {
|
|
611
|
+
if (!body.startsWith("<<", index)) return null;
|
|
612
|
+
const lineEnd = body.indexOf("\n", index + 2);
|
|
613
|
+
const end = body.indexOf(">>", index + 2);
|
|
614
|
+
if (end === -1 || (lineEnd !== -1 && end > lineEnd)) return null;
|
|
615
|
+
const authored = body.slice(index + 2, end);
|
|
616
|
+
if (!authored || authored !== authored.trim() || authored.startsWith("<")) return null;
|
|
617
|
+
return { authored, end };
|
|
618
|
+
};
|
|
619
|
+
|
|
580
620
|
export const parseChunk = (body, source, options = {}) => {
|
|
581
621
|
const nodes = [];
|
|
582
622
|
const diagnostics = [];
|
|
@@ -584,18 +624,23 @@ export const parseChunk = (body, source, options = {}) => {
|
|
|
584
624
|
let index = 0;
|
|
585
625
|
|
|
586
626
|
while (index < body.length) {
|
|
587
|
-
if (options.nowebReferences && body.startsWith("
|
|
588
|
-
|
|
589
|
-
if (
|
|
590
|
-
|
|
591
|
-
break;
|
|
627
|
+
if (options.nowebReferences && body.startsWith("@<<", index)) {
|
|
628
|
+
// `@<<` is noweb's escape for a literal `<<`; drop the `@`.
|
|
629
|
+
if (literalStart < index) {
|
|
630
|
+
nodes.push({ type: "literal", value: body.slice(literalStart, index), source: span(source, body, literalStart, index) });
|
|
592
631
|
}
|
|
632
|
+
literalStart = index + 1;
|
|
633
|
+
index += 3;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const noweb = options.nowebReferences ? nowebReferenceAt(body, index) : null;
|
|
637
|
+
if (noweb) {
|
|
638
|
+
const { end, authored } = noweb;
|
|
593
639
|
if (literalStart < index) {
|
|
594
640
|
nodes.push({ type: "literal", value: body.slice(literalStart, index), source: span(source, body, literalStart, index) });
|
|
595
641
|
}
|
|
596
|
-
const authored = body.slice(index + 2, end);
|
|
597
642
|
const expression = nowebExpression(authored, options);
|
|
598
|
-
const parsed = parseExpression(expression, source, index + 2, diagnostics);
|
|
643
|
+
const parsed = parseExpression(expression, source, index + 2, diagnostics, { text: body });
|
|
599
644
|
if (parsed) {
|
|
600
645
|
nodes.push({
|
|
601
646
|
...parsed,
|
|
@@ -608,9 +653,11 @@ export const parseChunk = (body, source, options = {}) => {
|
|
|
608
653
|
literalStart = index;
|
|
609
654
|
continue;
|
|
610
655
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
656
|
+
let counted = null;
|
|
657
|
+
if (options.underscoreReferences !== false && body[index] === "\\") {
|
|
658
|
+
countedReferencePattern.lastIndex = index;
|
|
659
|
+
counted = countedReferencePattern.exec(body);
|
|
660
|
+
}
|
|
614
661
|
if (counted) {
|
|
615
662
|
const quote = counted[2];
|
|
616
663
|
const expressionStart = index + counted[0].length;
|
|
@@ -630,7 +677,7 @@ export const parseChunk = (body, source, options = {}) => {
|
|
|
630
677
|
nodes.push({ type: "literal", value: body.slice(literalStart, index), source: span(source, body, literalStart, index) });
|
|
631
678
|
}
|
|
632
679
|
const expression = body.slice(expressionStart, end);
|
|
633
|
-
const parsed = parseExpression(expression, source, expressionStart, diagnostics, { allowDelay: false });
|
|
680
|
+
const parsed = parseExpression(expression, source, expressionStart, diagnostics, { allowDelay: false, text: body });
|
|
634
681
|
if (parsed) {
|
|
635
682
|
nodes.push({
|
|
636
683
|
type: "delay",
|
|
@@ -682,7 +729,7 @@ export const parseChunk = (body, source, options = {}) => {
|
|
|
682
729
|
nodes.push({ type: "literal", value: body.slice(literalStart, index), source: span(source, body, literalStart, index) });
|
|
683
730
|
}
|
|
684
731
|
const expression = body.slice(index + 2, end);
|
|
685
|
-
const parsed = parseExpression(expression, source, index + 2, diagnostics);
|
|
732
|
+
const parsed = parseExpression(expression, source, index + 2, diagnostics, { text: body });
|
|
686
733
|
if (parsed) nodes.push({ ...parsed, continuationIndent: continuationIndentAt(body, index) });
|
|
687
734
|
index = end + 1;
|
|
688
735
|
literalStart = index;
|
|
@@ -901,9 +948,12 @@ const applyTransform = (value, step, diagnostics, transforms, context = {}) => {
|
|
|
901
948
|
const transform = customTransform(transforms, step.name);
|
|
902
949
|
if (typeof transform === "function") {
|
|
903
950
|
try {
|
|
904
|
-
|
|
951
|
+
// `parts: true` invites a transform to describe which output came from
|
|
952
|
+
// its input or arguments; a plain string result is always accepted.
|
|
953
|
+
const result = transform(value, { ...context, arguments: clone(step.arguments), transform: step.name, parts: true });
|
|
905
954
|
if (typeof result === "string") return result;
|
|
906
|
-
|
|
955
|
+
if (Array.isArray(result?.parts)) return result;
|
|
956
|
+
diagnostics.push(diagnostic("RV121", "Transform " + step.name + " must return a string or { parts }.", step.source));
|
|
907
957
|
} catch (error) {
|
|
908
958
|
diagnostics.push(diagnostic("RV121", "Transform " + step.name + " failed: " + (error?.message ?? String(error)), step.source));
|
|
909
959
|
}
|
|
@@ -1235,14 +1285,52 @@ export const transformGraph = (pretransform, options = {}) => {
|
|
|
1235
1285
|
return (hash >>> 0).toString(36).toUpperCase();
|
|
1236
1286
|
};
|
|
1237
1287
|
|
|
1288
|
+
// Assemble a `{ parts }` transform result: input and argument ranges keep
|
|
1289
|
+
// their provenance; literal parts are attributed to the transform step.
|
|
1290
|
+
const mappedTransformParts = (input, parts, step, definition, argumentValues = []) => {
|
|
1291
|
+
const derivation = { kind: "transform", name: step.name, source: clone(step.source) };
|
|
1292
|
+
const argumentMapped = (index) => argumentValues[index] ??
|
|
1293
|
+
(typeof step.arguments[index] === "string" ? coarseMapped(step.arguments[index], step.source, definition.id, "transform", [derivation]) : null);
|
|
1294
|
+
const validRange = (text, start, end) => Number.isInteger(start) && Number.isInteger(end) && start >= 0 && start <= end && end <= text.length;
|
|
1295
|
+
let value = mappedValue();
|
|
1296
|
+
for (const part of parts) {
|
|
1297
|
+
let piece = null;
|
|
1298
|
+
if (typeof part === "string") {
|
|
1299
|
+
piece = coarseMapped(part, step.source, definition.id, "transform", [derivation]);
|
|
1300
|
+
} else if (Array.isArray(part?.input) && validRange(input.text, part.input[0], part.input[1])) {
|
|
1301
|
+
piece = withMappedDerivation(sliceMapped(input, part.input[0], part.input[1]), derivation);
|
|
1302
|
+
} else if (Number.isInteger(part?.argument) && typeof part.text === "string") {
|
|
1303
|
+
const source = argumentMapped(part.argument);
|
|
1304
|
+
piece = source
|
|
1305
|
+
? coarseDerivedMapped(part.text, step.source, definition.id, "transform", [derivation], source)
|
|
1306
|
+
: coarseMapped(part.text, step.source, definition.id, "transform", [derivation]);
|
|
1307
|
+
} else if (Number.isInteger(part?.argument)) {
|
|
1308
|
+
const source = argumentMapped(part.argument);
|
|
1309
|
+
if (source && validRange(source.text, part.start, part.end)) {
|
|
1310
|
+
piece = withMappedDerivation(sliceMapped(source, part.start, part.end), derivation);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
if (!piece) {
|
|
1314
|
+
diagnostics.push(diagnostic("RV121", "Transform " + step.name + " returned an invalid part.", step.source));
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
value = concatMapped(value, piece);
|
|
1318
|
+
}
|
|
1319
|
+
return value;
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1238
1322
|
const evaluateTransformMapped = (input, step, definition, context = {}) => {
|
|
1323
|
+
const { argumentValues, ...transformContext } = context;
|
|
1239
1324
|
const output = applyTransform(
|
|
1240
1325
|
input.text,
|
|
1241
1326
|
step,
|
|
1242
1327
|
diagnostics,
|
|
1243
1328
|
options.transforms,
|
|
1244
|
-
{ chunk: definition, ...
|
|
1329
|
+
{ chunk: definition, ...transformContext }
|
|
1245
1330
|
);
|
|
1331
|
+
if (typeof output !== "string") {
|
|
1332
|
+
return mappedTransformParts(input, output.parts, step, definition, argumentValues) ?? input;
|
|
1333
|
+
}
|
|
1246
1334
|
return mapBuiltinTransform(input, output, step, definition.id) ?? coarseDerivedMapped(
|
|
1247
1335
|
output,
|
|
1248
1336
|
step.source,
|
|
@@ -1358,8 +1446,16 @@ export const transformGraph = (pretransform, options = {}) => {
|
|
|
1358
1446
|
} else if (step.type === "chunk") {
|
|
1359
1447
|
value = evaluateExpression(step.value, owner);
|
|
1360
1448
|
} else if (step.type === "transform") {
|
|
1361
|
-
|
|
1362
|
-
|
|
1449
|
+
// Evaluate each ch()/text() argument once, keeping its mapped value so
|
|
1450
|
+
// a parts-aware transform can carry its provenance through.
|
|
1451
|
+
const argumentValues = step.arguments.map((argument) => {
|
|
1452
|
+
const command = argument?.kind === "ravel-command-argument" ? argument.command : null;
|
|
1453
|
+
if (command?.type === "chunk") return evaluateExpression(command.value, owner);
|
|
1454
|
+
if (command?.type === "text") return coarseMapped(command.value, command.source ?? step.source, owner.definition.id, "text");
|
|
1455
|
+
return null;
|
|
1456
|
+
});
|
|
1457
|
+
const argumentsValue = step.arguments.map((argument, index) => argumentValues[index]?.text ?? evaluateArgument(argument, owner));
|
|
1458
|
+
value = evaluateTransformMapped(value, { ...step, arguments: argumentsValue }, owner.definition, { argumentValues });
|
|
1363
1459
|
}
|
|
1364
1460
|
}
|
|
1365
1461
|
return value;
|