diff options
Diffstat (limited to 'src/data/composite.js')
-rw-r--r-- | src/data/composite.js | 216 |
1 files changed, 192 insertions, 24 deletions
diff --git a/src/data/composite.js b/src/data/composite.js index 7a98c424..ea7a3480 100644 --- a/src/data/composite.js +++ b/src/data/composite.js @@ -29,6 +29,7 @@ input.value = _valueIntoToken('input.value'); input.dependency = _valueIntoToken('input.dependency'); input.myself = () => Symbol.for(`hsmusic.composite.input.myself`); +input.thisProperty = () => Symbol.for('hsmusic.composite.input.thisProperty'); input.updateValue = _valueIntoToken('input.updateValue'); @@ -284,6 +285,7 @@ export function templateCompositeFrom(description) { 'input.value', 'input.dependency', 'input.myself', + 'input.thisProperty', 'input.updateValue', ].includes(tokenShape)) { expectedValueProvidingTokenInputNames.push(name); @@ -567,6 +569,8 @@ export function compositeFrom(description) { return token; case 'input.myself': return 'this'; + case 'input.thisProperty': + return 'thisProperty'; default: return null; } @@ -721,6 +725,8 @@ export function compositeFrom(description) { return (tokenValue.startsWith('#') ? null : tokenValue); case 'input.myself': return 'this'; + case 'input.thisProperty': + return 'thisProperty'; default: return null; } @@ -752,6 +758,9 @@ export function compositeFrom(description) { anyStepsUseUpdateValue || anyStepsUpdate; + const stepsFirstTimeCalling = + Array.from({length: steps.length}).fill(true); + const stepEntries = stitchArrays({ step: steps, stepComposes: stepsCompose, @@ -774,16 +783,9 @@ export function compositeFrom(description) { (step.annotation ? ` (${step.annotation})` : ``); aggregate.nest({message}, ({push}) => { - if (isBase && stepComposes !== compositionNests) { - return push(new TypeError( - (compositionNests - ? `Base must compose, this composition is nestable` - : `Base must not compose, this composition isn't nestable`))); - } else if (!isBase && !stepComposes) { + if (!isBase && !stepComposes) { return push(new TypeError( - (compositionNests - ? `All steps must compose` - : `All steps (except base) must compose`))); + `All steps leading up to base must compose`)); } if ( @@ -877,6 +879,8 @@ export function compositeFrom(description) { return valueSoFar; case 'input.myself': return initialDependencies['this']; + case 'input.thisProperty': + return initialDependencies['thisProperty']; case 'input': return initialDependencies[token]; default: @@ -907,8 +911,16 @@ export function compositeFrom(description) { debug(() => colors.bright(`begin composition - not transforming`)); } - for (let i = 0; i < steps.length; i++) { - const step = steps[i]; + for ( + const [i, { + step, + stepComposes, + }] of + stitchArrays({ + step: steps, + stepComposes: stepsCompose, + }).entries() + ) { const isBase = i === steps.length - 1; debug(() => [ @@ -968,7 +980,16 @@ export function compositeFrom(description) { (expectingTransform ? {[input.updateValue()]: valueSoFar} : {}), - [input.myself()]: initialDependencies?.['this'] ?? null, + + [input.myself()]: + (initialDependencies && Object.hasOwn(initialDependencies, 'this') + ? initialDependencies.this + : null), + + [input.thisProperty()]: + (initialDependencies && Object.hasOwn(initialDependencies, 'thisProperty') + ? initialDependencies.thisProperty + : null), }; const selectDependencies = @@ -983,6 +1004,8 @@ export function compositeFrom(description) { return dependency; case 'input.myself': return input.myself(); + case 'input.thisProperty': + return input.thisProperty(); case 'input.dependency': return tokenValue; case 'input.updateValue': @@ -1016,26 +1039,175 @@ export function compositeFrom(description) { const naturalEvaluate = () => { const [name, ...argsLayout] = getExpectedEvaluation(); - let args; + let args = argsLayout; - if (isBase && !compositionNests) { - args = - argsLayout.filter(arg => arg !== continuationSymbol); + let effectiveDependencies; + let reviewAccessedDependencies; + + if (stepsFirstTimeCalling[i]) { + const expressedDependencies = + selectDependencies; + + const remainingDependencies = + new Set(expressedDependencies); + + const unavailableDependencies = []; + const accessedDependencies = []; + + effectiveDependencies = + new Proxy(filteredDependencies, { + get(target, key) { + accessedDependencies.push(key); + remainingDependencies.delete(key); + + const value = target[key]; + + if (value === undefined) { + unavailableDependencies.push(key); + } + + return value; + }, + }); + + reviewAccessedDependencies = () => { + const topAggregate = + openAggregate({ + message: `Errors in accessed dependencies`, + }); + + const showDependency = dependency => + (isInputToken(dependency) + ? getInputTokenShape(dependency) + + `(` + + inspect(getInputTokenValue(dependency), {compact: true}) + + ')' + : dependency.toString()); + + let anyErrors = false; + + for (const dependency of remainingDependencies) { + topAggregate.push(new Error( + `Expected to access ${showDependency(dependency)}`)); + + anyErrors = true; + } + + for (const dependency of unavailableDependencies) { + const subAggregate = + openAggregate({ + message: + `Accessed ${showDependency(dependency)}, which is unavailable`, + }); + + let reason = false; + + if (!expressedDependencies.includes(dependency)) { + subAggregate.push(new Error( + `Missing from step's expressed dependencies`)); + reason = true; + } + + if (filterableDependencies[dependency] === undefined) { + subAggregate.push( + new Error( + `Not available` + + (isInputToken(dependency) + ? ` in input()-type dependencies` + : dependency.startsWith('#') + ? ` in local dependencies` + : ` on object dependencies`))); + reason = true; + } + + if (!reason) { + subAggregate.push(new Error( + `Not sure why this is unavailable, sorry!`)); + } + + topAggregate.call(subAggregate.close); + + anyErrors = true; + } + + if (anyErrors) { + topAggregate.push(new Error( + `These dependencies, in total, were accessed:` + + (empty(accessedDependencies) + ? ` (none)` + : accessedDependencies.length === 1 + ? showDependency(accessedDependencies[0]) + : `\n` + + accessedDependencies + .map(showDependency) + .map(line => ` - ${line}`) + .join('\n')))); + } + + topAggregate.close(); + }; } else { + effectiveDependencies = filteredDependencies; + reviewAccessedDependencies = null; + } + + args = + args.map(arg => + (arg === filteredDependencies + ? effectiveDependencies + : arg)); + + if (stepComposes) { let continuation; ({continuation, continuationStorage} = _prepareContinuation(callingTransformForThisStep)); args = - argsLayout.map(arg => + args.map(arg => (arg === continuationSymbol ? continuation : arg)); + } else { + args = + args.filter(arg => arg !== continuationSymbol); } - return expose[name](...args); - } + let stepError; + try { + return expose[name](...args); + } catch (error) { + stepError = error; + } finally { + stepsFirstTimeCalling[i] = false; + + let reviewError; + if (reviewAccessedDependencies) { + try { + reviewAccessedDependencies(); + } catch (error) { + reviewError = error; + } + } + + const stepPart = + `step ${i+1}` + + (isBase + ? ` (base)` + : ` of ${steps.length}`) + + (step.annotation ? `, ${step.annotation}` : ``); + + if (stepError && reviewError) { + throw new AggregateError( + [stepError, reviewError], + `Errors in ${stepPart}`); + } else if (stepError || reviewError) { + throw new Error( + `Error in ${stepPart}`, + {cause: stepError || reviewError}); + } + } + }; switch (step.cache) { // Warning! Highly WIP! @@ -1091,11 +1263,6 @@ export function compositeFrom(description) { if (result !== continuationSymbol) { debug(() => [`step #${i+1} - result: exit (inferred) ->`, result]); - - if (compositionNests) { - throw new TypeError(`Inferred early-exit is disallowed in nested compositions`); - } - debug(() => colors.bright(`end composition - exit (inferred)`)); return result; @@ -1216,6 +1383,7 @@ export function compositeFrom(description) { `Error computing composition` + (annotation ? ` ${annotation}` : '')); error.cause = thrownError; + error[Symbol.for('hsmusic.aggregate.translucent')] = true; throw error; } }; |